Merge m-c to oak
authorRobert Strong <robert.bugzilla@gmail.com>
Wed, 25 May 2016 15:45:35 -0700
changeset 491552 b5a168aa123e35e090bd32d49117d497e414b37b
parent 491551 c9e505ae0ba931ea3989607e3f337b0dbfb54a4f (current diff)
parent 371122 8d0aadfe7da782d415363880008b4ca027686137 (diff)
child 491553 f38999684d8c02dba1befd26a8d9f0d6056ef928
push id47343
push userbmo:dothayer@mozilla.com
push dateWed, 01 Mar 2017 22:58:58 +0000
milestone49.0a1
Merge m-c to oak
browser/app/profile/firefox.js
ipc/glue/GeckoChildProcessHost.cpp
testing/marionette/emulator.js
testing/marionette/harness/marionette/b2g_update_test.py
testing/marionette/harness/marionette/runner/mixins/b2g.py
testing/marionette/harness/marionette/tests/unit/test_emulator.py
testing/mochitest/runtests.py
toolkit/crashreporter/nsExceptionHandler.cpp
toolkit/locales/en-US/chrome/mozapps/update/updates.dtd
toolkit/mozapps/update/content/updates.js
toolkit/mozapps/update/content/updates.xul
toolkit/mozapps/update/nsIUpdateService.idl
toolkit/mozapps/update/nsUpdateService.js
toolkit/mozapps/update/tests/chrome/test_0091_installed.xul
toolkit/mozapps/update/tests/chrome/utils.js
toolkit/mozapps/update/tests/data/shared.js
toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
toolkit/mozapps/update/tests/unit_aus_update/uiOnlyAllowOneWindow.js
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -59,17 +59,31 @@ DocAccessibleParent::RecvShowEvent(const
   for (uint32_t i = 0; i < consumed; i++) {
     uint64_t id = aData.NewTree()[i].ID();
     MOZ_ASSERT(mAccessibles.GetEntry(id));
   }
 #endif
 
   MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
 
-  ProxyShowHideEvent(parent->ChildAt(newChildIdx), parent, true, aFromUser);
+  ProxyAccessible* target = parent->ChildAt(newChildIdx);
+  ProxyShowHideEvent(target, parent, true, aFromUser);
+
+  if (!nsCoreUtils::AccEventObserversExist()) {
+    return true;
+  }
+
+  uint32_t type = nsIAccessibleEvent::EVENT_SHOW;
+  xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
+  xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+  nsIDOMNode* node = nullptr;
+  RefPtr<xpcAccEvent> event = new xpcAccEvent(type, xpcAcc, doc, node,
+                                              aFromUser);
+  nsCoreUtils::DispatchAccEvent(Move(event));
+
   return true;
 }
 
 uint32_t
 DocAccessibleParent::AddSubtree(ProxyAccessible* aParent,
                                 const nsTArray<a11y::AccessibleData>& aNewTree,
                                 uint32_t aIdx, uint32_t aIdxInParent)
 {
@@ -136,21 +150,41 @@ DocAccessibleParent::RecvHideEvent(const
   ProxyAccessible* root = rootEntry->mProxy;
   if (!root) {
     NS_ERROR("invalid root being removed!");
     return true;
   }
 
   ProxyAccessible* parent = root->Parent();
   ProxyShowHideEvent(root, parent, false, aFromUser);
+
+  RefPtr<xpcAccHideEvent> event = nullptr;
+  if (nsCoreUtils::AccEventObserversExist()) {
+    uint32_t type = nsIAccessibleEvent::EVENT_HIDE;
+    xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(root);
+    xpcAccessibleGeneric* xpcParent = GetXPCAccessible(parent);
+    ProxyAccessible* next = root->NextSibling();
+    xpcAccessibleGeneric* xpcNext = next ? GetXPCAccessible(next) : nullptr;
+    ProxyAccessible* prev = root->PrevSibling();
+    xpcAccessibleGeneric* xpcPrev = prev ? GetXPCAccessible(prev) : nullptr;
+    xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+    nsIDOMNode* node = nullptr;
+    event = new xpcAccHideEvent(type, xpcAcc, doc, node, aFromUser, xpcParent,
+                                xpcNext, xpcPrev);
+  }
+
   parent->RemoveChild(root);
   root->Shutdown();
 
   MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
 
+  if (event) {
+    nsCoreUtils::DispatchAccEvent(Move(event));
+  }
+
   return true;
 }
 
 bool
 DocAccessibleParent::RecvEvent(const uint64_t& aID, const uint32_t& aEventType)
 {
   ProxyAccessible* proxy = GetAccessible(aID);
   if (!proxy) {
--- a/accessible/tests/browser/browser.ini
+++ b/accessible/tests/browser/browser.ini
@@ -15,29 +15,25 @@ support-files =
 
 # Caching tests
 [browser_caching_name.js]
 skip-if = e10s
 
 # Events tests
 [browser_events_caretmove.js]
 [browser_events_hide.js]
-skip-if = e10s
 [browser_events_show.js]
-skip-if = e10s
 [browser_events_statechange.js]
 [browser_events_textchange.js]
 
 # Tree update tests
 [browser_treeupdate_ariadialog.js]
-skip-if = e10s
 [browser_treeupdate_ariaowns.js]
 skip-if = e10s
 [browser_treeupdate_canvas.js]
-skip-if = e10s
 [browser_treeupdate_cssoverflow.js]
 [browser_treeupdate_doc.js]
 skip-if = e10s
 [browser_treeupdate_gencontent.js]
 [browser_treeupdate_hidden.js]
 [browser_treeupdate_imagemap.js]
 skip-if = e10s
 [browser_treeupdate_list.js]
--- a/accessible/tests/browser/browser_events_hide.js
+++ b/accessible/tests/browser/browser_events_hide.js
@@ -10,23 +10,26 @@
  * Test hide event and its interface:
  *   - targetParent
  *   - targetNextSibling
  *   - targetPrevSibling
  */
 addAccessibleTask(`
   <div id="parent">
     <div id="previous"></div>
-    <div id="div"></div>
+    <div id="to-hide"></div>
     <div id="next"></div>
-  </div>`, function*(browser) {
-  let onHide = waitForEvent(EVENT_HIDE, 'div');
-  yield invokeSetStyle(browser, 'div', 'visibility', 'hidden');
-  let event = yield onHide;
-  let hideEvent = event.QueryInterface(Ci.nsIAccessibleHideEvent);
+  </div>`,
+  function*(browser, accDoc) {
+    let acc = findAccessibleChildByID(accDoc, 'to-hide');
+    let onHide = waitForEvent(EVENT_HIDE, acc);
+    yield invokeSetStyle(browser, 'to-hide', 'visibility', 'hidden');
+    let event = yield onHide;
+    let hideEvent = event.QueryInterface(Ci.nsIAccessibleHideEvent);
 
-  is(getAccessibleDOMNodeID(hideEvent.targetParent), 'parent',
-    'Correct target parent.');
-  is(getAccessibleDOMNodeID(hideEvent.targetNextSibling), 'next',
-    'Correct target next sibling.');
-  is(getAccessibleDOMNodeID(hideEvent.targetPrevSibling), 'previous',
-    'Correct target previous sibling.');
-});
+    is(getAccessibleDOMNodeID(hideEvent.targetParent), 'parent',
+      'Correct target parent.');
+    is(getAccessibleDOMNodeID(hideEvent.targetNextSibling), 'next',
+      'Correct target next sibling.');
+    is(getAccessibleDOMNodeID(hideEvent.targetPrevSibling), 'previous',
+      'Correct target previous sibling.');
+  }
+);
--- a/accessible/tests/browser/browser_treeupdate_doc.js
+++ b/accessible/tests/browser/browser_treeupdate_doc.js
@@ -28,203 +28,203 @@ addAccessibleTask(`
   /* ================= Initial tree check =================================== */
   let tree = {
     role: ROLE_DOCUMENT,
     children: [ ]
   };
   testAccessibleTree(iframe, tree);
 
   /* ================= Write iframe document ================================ */
-  let onReorder = waitForEvent(EVENT_REORDER, id);
+  let reorderEventPromise = waitForEvent(EVENT_REORDER, id);
   yield ContentTask.spawn(browser, id, id => {
     let docNode = content.document.getElementById('iframe').contentDocument;
     let newHTMLNode = docNode.createElement('html');
     let newBodyNode = docNode.createElement('body');
     let newTextNode = docNode.createTextNode('New Wave');
     newBodyNode.id = id;
     newBodyNode.appendChild(newTextNode);
     newHTMLNode.appendChild(newBodyNode);
     docNode.replaceChild(newHTMLNode, docNode.documentElement);
   });
-  yield onReorder;
+  yield reorderEventPromise;
 
   tree = {
     role: ROLE_DOCUMENT,
     children: [
       {
         role: ROLE_TEXT_LEAF,
         name: 'New Wave'
       }
     ]
   };
   testAccessibleTree(iframe, tree);
 
   /* ================= Replace iframe HTML element ========================== */
-  onReorder = waitForEvent(EVENT_REORDER, id);
+  reorderEventPromise = waitForEvent(EVENT_REORDER, id);
   yield ContentTask.spawn(browser, id, id => {
     let docNode = content.document.getElementById('iframe').contentDocument;
     // We can't use open/write/close outside of iframe document because of
     // security error.
     let script = docNode.createElement('script');
     script.textContent = `
       document.open();
       document.write('<body id="${id}">hello</body>');
       document.close();`;
     docNode.body.appendChild(script);
   });
-  yield onReorder;
+  yield reorderEventPromise;
 
   tree = {
     role: ROLE_DOCUMENT,
     children: [
       {
         role: ROLE_TEXT_LEAF,
         name: 'hello'
       }
     ]
   };
   testAccessibleTree(iframe, tree);
 
   /* ================= Replace iframe body ================================== */
-  onReorder = waitForEvent(EVENT_REORDER, id);
+  reorderEventPromise = waitForEvent(EVENT_REORDER, id);
   yield ContentTask.spawn(browser, id, id => {
     let docNode = content.document.getElementById('iframe').contentDocument;
     let newBodyNode = docNode.createElement('body');
     let newTextNode = docNode.createTextNode('New Hello');
     newBodyNode.id = id;
     newBodyNode.appendChild(newTextNode);
     newBodyNode.setAttribute('role', 'button');
     docNode.documentElement.replaceChild(newBodyNode, docNode.body);
   });
-  yield onReorder;
+  yield reorderEventPromise;
 
   tree = {
     role: ROLE_PUSHBUTTON,
     children: [
       {
         role: ROLE_TEXT_LEAF,
         name: 'New Hello'
       }
     ]
   };
   testAccessibleTree(iframe, tree);
 
   /* ================= Open iframe document ================================= */
-  onReorder = waitForEvent(EVENT_REORDER, id);
+  reorderEventPromise = waitForEvent(EVENT_REORDER, id);
   yield ContentTask.spawn(browser, id, id => {
     // Open document.
     let docNode = content.document.getElementById('iframe').contentDocument;
     let script = docNode.createElement('script');
     script.textContent = `
       function closeMe() {
         document.write('Works?');
         document.close();
       }
       window.closeMe = closeMe;
       document.open();
       document.write('<body id="${id}"></body>');`;
     docNode.body.appendChild(script);
   });
-  yield onReorder;
+  yield reorderEventPromise;
 
   tree = {
     role: ROLE_DOCUMENT,
     children: [ ]
   };
   testAccessibleTree(iframe, tree);
 
   /* ================= Close iframe document ================================ */
-  onReorder = waitForEvent(EVENT_REORDER, id);
+  reorderEventPromise = waitForEvent(EVENT_REORDER, id);
   yield ContentTask.spawn(browser, {}, () => {
     // Write and close document.
     let docNode = content.document.getElementById('iframe').contentDocument;
     docNode.write('Works?');
     docNode.close();
   });
-  yield onReorder;
+  yield reorderEventPromise;
 
   tree = {
     role: ROLE_DOCUMENT,
     children: [
       {
         role: ROLE_TEXT_LEAF,
         name: 'Works?'
       }
     ]
   };
   testAccessibleTree(iframe, tree);
 
   /* ================= Remove HTML from iframe document ===================== */
-  onReorder = waitForEvent(EVENT_REORDER);
+  reorderEventPromise = waitForEvent(EVENT_REORDER, iframe);
   yield ContentTask.spawn(browser, {}, () => {
     // Remove HTML element.
     let docNode = content.document.getElementById('iframe').contentDocument;
     docNode.removeChild(docNode.firstChild);
   });
-  let event = yield onReorder;
+  let event = yield reorderEventPromise;
 
   ok(event.accessible instanceof nsIAccessibleDocument,
     'Reorder should happen on the document');
   tree = {
     role: ROLE_DOCUMENT,
     children: [ ]
   };
   testAccessibleTree(iframe, tree);
 
   /* ================= Insert HTML to iframe document ======================= */
-  onReorder = waitForEvent(EVENT_REORDER, id);
+  reorderEventPromise = waitForEvent(EVENT_REORDER, id);
   yield ContentTask.spawn(browser, id, id => {
     // Insert HTML element.
     let docNode = content.document.getElementById('iframe').contentDocument;
     let html = docNode.createElement('html');
     let body = docNode.createElement('body');
     let text = docNode.createTextNode('Haha');
     body.appendChild(text);
     body.id = id;
     html.appendChild(body);
     docNode.appendChild(html);
   });
-  yield onReorder;
+  yield reorderEventPromise;
 
   tree = {
     role: ROLE_DOCUMENT,
     children: [
       {
         role: ROLE_TEXT_LEAF,
         name: 'Haha'
       }
     ]
   };
   testAccessibleTree(iframe, tree);
 
   /* ================= Remove body from iframe document ===================== */
-  onReorder = waitForEvent(EVENT_REORDER);
+  reorderEventPromise = waitForEvent(EVENT_REORDER, iframe);
   yield ContentTask.spawn(browser, {}, () => {
     // Remove body element.
     let docNode = content.document.getElementById('iframe').contentDocument;
     docNode.documentElement.removeChild(docNode.body);
   });
-  event = yield onReorder;
+  event = yield reorderEventPromise;
 
   ok(event.accessible instanceof nsIAccessibleDocument,
     'Reorder should happen on the document');
   tree = {
     role: ROLE_DOCUMENT,
     children: [ ]
   };
   testAccessibleTree(iframe, tree);
 
   /* ================ Insert element under document element while body missed */
-  onReorder = waitForEvent(EVENT_REORDER);
+  reorderEventPromise = waitForEvent(EVENT_REORDER, iframe);
   yield ContentTask.spawn(browser, {}, () => {
     let docNode = content.document.getElementById('iframe').contentDocument;
     let inputNode = content.window.inputNode = docNode.createElement('input');
     docNode.documentElement.appendChild(inputNode);
   });
-  event = yield onReorder;
+  event = yield reorderEventPromise;
 
   ok(event.accessible instanceof nsIAccessibleDocument,
     'Reorder should happen on the document');
   tree = {
     DOCUMENT: [
       { ENTRY: [ ] }
     ]
   };
@@ -232,68 +232,68 @@ addAccessibleTask(`
 
   yield ContentTask.spawn(browser, {}, () => {
     let docNode = content.document.getElementById('iframe').contentDocument;
     // Remove aftermath of this test before next test starts.
     docNode.documentElement.removeChild(content.window.inputNode);
   });
 
   /* ================= Insert body to iframe document ======================= */
-  onReorder = waitForEvent(EVENT_REORDER, id);
+  reorderEventPromise = waitForEvent(EVENT_REORDER, id);
   yield ContentTask.spawn(browser, id, id => {
     // Write and close document.
     let docNode = content.document.getElementById('iframe').contentDocument;
     // Insert body element.
     let body = docNode.createElement('body');
     let text = docNode.createTextNode('Yo ho ho i butylka roma!');
     body.appendChild(text);
     body.id = id;
     docNode.documentElement.appendChild(body);
   });
-  yield onReorder;
+  yield reorderEventPromise;
 
   tree = {
     role: ROLE_DOCUMENT,
     children: [
       {
         role: ROLE_TEXT_LEAF,
         name: 'Yo ho ho i butylka roma!'
       }
     ]
   };
   testAccessibleTree(iframe, tree);
 
   /* ================= Change source ======================================== */
-  onReorder = waitForEvent(EVENT_REORDER, 'iframe');
+  reorderEventPromise = waitForEvent(EVENT_REORDER, 'iframe');
   yield invokeSetAttribute(browser, 'iframe', 'src',
     `data:text/html,<html><body id="${id}"><input></body></html>`);
-  event = yield onReorder;
+  event = yield reorderEventPromise;
 
   tree = {
     INTERNAL_FRAME: [
       { DOCUMENT: [
         { ENTRY: [ ] }
       ] }
     ]
   };
   testAccessibleTree(event.accessible, tree);
   iframe = findAccessibleChildByID(event.accessible, id);
 
   /* ================= Replace iframe body on ARIA role body ================ */
-  onReorder = waitForEvent(EVENT_REORDER, id);
+  reorderEventPromise = waitForEvent(EVENT_REORDER, id);
   yield ContentTask.spawn(browser, id, id => {
     let docNode = content.document.getElementById('iframe').contentDocument;
     let newBodyNode = docNode.createElement('body');
     let newTextNode = docNode.createTextNode('New Hello');
     newBodyNode.appendChild(newTextNode);
     newBodyNode.setAttribute('role', 'button');
     newBodyNode.id = id;
     docNode.documentElement.replaceChild(newBodyNode, docNode.body);
   });
-  yield onReorder;
+  yield reorderEventPromise;
 
   tree = {
     role: ROLE_PUSHBUTTON,
     children: [
       {
         role: ROLE_TEXT_LEAF,
         name: 'New Hello'
       }
--- a/accessible/tests/browser/events.js
+++ b/accessible/tests/browser/events.js
@@ -37,38 +37,54 @@ function eventToString(event) {
     info += `, start: ${event.start}, length: ${event.length}, ${tcType} text: ${event.modifiedText}`;
   }
 
   info += `. Target: ${prettyName(event.accessible)}`;
   return info;
 }
 
 /**
- * A helper function that waits for an accessible event of certain type that
- * belongs to a certain DOMNode (defined by its id).
- * @param  {String}  id         expected content element id for the event
- * @param  {Number}  eventType  expected accessible event type
- * @return {Promise}            promise that resolves to an event
+ * A helper function that returns a promise that resolves when an accessible
+ * event of the given type with the given target (defined by its id or
+ * accessible) is observed.
+ * @param  {String|nsIAccessible}  expectedIdOrAcc  expected content element id
+ *                                                  for the event
+ * @param  {Number}                eventType        expected accessible event
+ *                                                  type
+ * @return {Promise}                                promise that resolves to an
+ *                                                  event
  */
-function waitForEvent(eventType, id) {
+function waitForEvent(eventType, expectedIdOrAcc) {
   return new Promise(resolve => {
     let eventObserver = {
       observe(subject, topic, data) {
         if (topic !== 'accessible-event') {
           return;
         }
 
         let event = subject.QueryInterface(nsIAccessibleEvent);
         Logger.log(eventToString(event));
 
-        let domID = getAccessibleDOMNodeID(event.accessible);
-        // If event's accessible does not match expected event type or DOMNode
-        // id, skip thie event.
-        if (domID === id && event.eventType === eventType) {
-          Logger.log(`Correct event DOMNode id: ${id}`);
+        // If event type does not match expected type, skip the event.
+        if (event.eventType !== eventType) {
+          return;
+        }
+
+        let acc = event.accessible;
+        let id = getAccessibleDOMNodeID(acc);
+        let isID = typeof expectedIdOrAcc === 'string';
+        let actualIdOrAcc = isID ? id : acc;
+        // If event's accessible does not match expected DOMNode id or
+        // accessible, skip the event.
+        if (actualIdOrAcc === expectedIdOrAcc) {
+          if (isID) {
+            Logger.log(`Correct event DOMNode id: ${id}`);
+          } else {
+            Logger.log(`Correct event accessible: ${prettyName(acc)}`);
+          }
           Logger.log(`Correct event type: ${eventTypeToString(eventType)}`);
           ok(event.accessibleDocument instanceof nsIAccessibleDocument,
             'Accessible document present.');
 
           Services.obs.removeObserver(this, 'accessible-event');
           resolve(event);
         }
       }
--- a/addon-sdk/source/test/jetpack-package.ini
+++ b/addon-sdk/source/test/jetpack-package.ini
@@ -32,16 +32,17 @@ support-files =
 [test-base64.js]
 [test-bootstrap.js]
 [test-browser-events.js]
 [test-buffer.js]
 [test-byte-streams.js]
 [test-child_process.js]
 [test-chrome.js]
 [test-clipboard.js]
+subsuite = clipboard
 [test-collection.js]
 [test-commonjs-test-adapter.js]
 [test-content-events.js]
 [test-content-script.js]
 [test-content-sync-worker.js]
 [test-content-worker.js]
 [test-context-menu.js]
 [test-context-menu@2.js]
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -144,23 +144,16 @@ pref("app.update.url", "https://aus5.moz
 //pref("app.update.url.override", "");
 
 // app.update.interval is in branding section
 // app.update.promptWaitTime is in branding section
 
 // Show the Update Checking/Ready UI when the user was idle for x seconds
 pref("app.update.idletime", 60);
 
-// Whether or not we show a dialog box informing the user that the update was
-// successfully applied. This is off in Firefox by default since we show a
-// upgrade start page instead! Other apps may wish to show this UI, and supply
-// a whatsNewURL field in their brand.properties that contains a link to a page
-// which tells users what's new in this new update.
-pref("app.update.showInstalledUI", false);
-
 // Whether or not to attempt using the service for updates.
 #ifdef MOZ_MAINTENANCE_SERVICE
 pref("app.update.service.enabled", true);
 #endif
 
 // Symmetric (can be overridden by individual extensions) update preferences.
 // e.g.
 //  extensions.{GUID}.update.enabled
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -154,16 +154,17 @@ tags = audiochannel
 skip-if = os == "mac" # The Fitt's Law back button is not supported on OS X
 [browser_beforeunload_duplicate_dialogs.js]
 [browser_blob-channelname.js]
 [browser_bookmark_popup.js]
 skip-if = (os == "linux" && debug) # mouseover not reliable on linux debug builds
 [browser_bookmark_titles.js]
 skip-if = buildapp == 'mulet' || toolkit == "windows" # Disabled on Windows due to frequent failures (bugs 825739, 841341)
 [browser_bug321000.js]
+subsuite = clipboard
 skip-if = true # browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
 [browser_bug356571.js]
 [browser_bug380960.js]
 [browser_bug386835.js]
 [browser_bug406216.js]
 [browser_bug408415.js]
 [browser_bug409481.js]
 [browser_bug409624.js]
@@ -198,31 +199,33 @@ skip-if = buildapp == 'mulet'
 [browser_bug495058.js]
 [browser_bug517902.js]
 skip-if = (os == 'linux' && e10s) # bug 1161699
 [browser_bug519216.js]
 [browser_bug520538.js]
 [browser_bug521216.js]
 [browser_bug533232.js]
 [browser_bug537013.js]
+subsuite = clipboard
 skip-if = buildapp == 'mulet' || e10s # Bug 1134458 - Find bar doesn't work correctly in a detached tab
 [browser_bug537474.js]
 [browser_bug550565.js]
 [browser_bug553455.js]
 skip-if = buildapp == 'mulet' # Bug 1066070 - I don't think either popup notifications nor addon install stuff works on mulet?
 [browser_bug555224.js]
 [browser_bug555767.js]
 [browser_bug559991.js]
 [browser_bug561636.js]
 skip-if = os == 'win' # bug 1057615
 [browser_bug563588.js]
 [browser_bug565575.js]
 [browser_bug565667.js]
 skip-if = toolkit != "cocoa"
 [browser_bug567306.js]
+subsuite = clipboard
 [browser_bug575561.js]
 [browser_bug575830.js]
 [browser_bug577121.js]
 [browser_bug578534.js]
 [browser_bug579872.js]
 [browser_bug580638.js]
 [browser_bug580956.js]
 [browser_bug581242.js]
@@ -274,16 +277,17 @@ tags = mcb
 [browser_mixedContentFramesOnHttp.js]
 tags = mcb
 [browser_bug970746.js]
 [browser_bug1015721.js]
 skip-if = os == 'win'
 [browser_bug1064280_changeUrlInPinnedTab.js]
 [browser_accesskeys.js]
 [browser_clipboard.js]
+subsuite = clipboard
 [browser_clipboard_pastefile.js]
 [browser_contentAreaClick.js]
 [browser_contextmenu.js]
 tags = fullscreen
 skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558
 [browser_contextmenu_input.js]
 skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558
 [browser_ctrlTab.js]
@@ -327,16 +331,17 @@ skip-if = buildapp == 'mulet'
 [browser_keywordBookmarklets.js]
 [browser_keywordSearch.js]
 [browser_keywordSearch_postData.js]
 [browser_lastAccessedTab.js]
 skip-if = toolkit == "windows" # Disabled on Windows due to frequent failures (bug 969405)
 [browser_menuButtonFitts.js]
 skip-if = os != "win" # The Fitts Law menu button is only supported on Windows (bug 969376)
 [browser_middleMouse_noJSPaste.js]
+subsuite = clipboard
 [browser_minimize.js]
 [browser_misused_characters_in_strings.js]
 [browser_mixedcontent_securityflags.js]
 tags = mcb
 [browser_offlineQuotaNotification.js]
 skip-if = buildapp == 'mulet'
 [browser_feed_discovery.js]
 support-files = feed_discovery.html
--- a/browser/base/content/test/urlbar/browser.ini
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -19,35 +19,38 @@ skip-if = (os == "linux" || os == "mac")
 [browser_bug1104165-switchtab-decodeuri.js]
 [browser_bug1003461-switchtab-override.js]
 [browser_bug1024133-switchtab-override-keynav.js]
 [browser_bug1025195_switchToTabHavingURI_aOpenParams.js]
 [browser_bug1070778.js]
 [browser_bug1225194-remotetab.js]
 [browser_bug304198.js]
 [browser_bug556061.js]
+subsuite = clipboard
 [browser_bug562649.js]
 [browser_bug623155.js]
 support-files =
   redirect_bug623155.sjs
 [browser_bug783614.js]
 [browser_canonizeURL.js]
 [browser_locationBarCommand.js]
 skip-if = os == "linux" # Linux: Intermittent failures, bug 917535
 [browser_locationBarExternalLoad.js]
 [browser_moz_action_link.js]
 [browser_removeUnsafeProtocolsFromURLBarPaste.js]
+subsuite = clipboard
 [browser_search_favicon.js]
 [browser_tabMatchesInAwesomebar.js]
 support-files =
   moz.png
 [browser_tabMatchesInAwesomebar_perwindowpb.js]
 skip-if = os == 'linux' # Bug 1104755
 [browser_urlbarAutoFillTrimURLs.js]
 [browser_urlbarCopying.js]
+subsuite = clipboard
 support-files =
   authenticate.sjs
 [browser_urlbarDecode.js]
 [browser_urlbarDelete.js]
 [browser_urlbarEnter.js]
 [browser_urlbarEnterAfterMouseOver.js]
 skip-if = os == "linux" # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
 [browser_urlbarHashChangeProxyState.js]
@@ -64,26 +67,28 @@ support-files =
   searchSuggestionEngine.xml
   searchSuggestionEngine.sjs
 [browser_urlbarSearchTelemetry.js]
 support-files =
   searchSuggestionEngine.xml
   searchSuggestionEngine.sjs
 [browser_urlbarStop.js]
 [browser_urlbarTrimURLs.js]
+subsuite = clipboard
 [browser_urlbarUpdateForDomainCompletion.js]
 [browser_urlbar_autoFill_backspaced.js]
 [browser_urlbar_blanking.js]
 support-files =
   file_blank_but_not_blank.html
 [browser_urlbar_locationchange_urlbar_edit_dos.js]
 support-files =
   file_urlbar_edit_dos.html
 [browser_urlbar_searchsettings.js]
 [browser_urlbar_stop_pending.js]
 support-files =
   slow-page.sjs
 [browser_urlbar_remoteness_switch.js]
 run-if = e10s
 [browser_urlHighlight.js]
 [browser_wyciwyg_urlbarCopying.js]
+subsuite = clipboard
 support-files =
   test_wyciwyg_copying.html
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -61,28 +61,31 @@ skip-if = os == "mac"
 [browser_941083_invalidate_wrapper_cache_createWidget.js]
 [browser_942581_unregisterArea_keeps_placements.js]
 [browser_943683_migration_test.js]
 [browser_944887_destroyWidget_should_destroy_in_palette.js]
 [browser_945739_showInPrivateBrowsing_customize_mode.js]
 [browser_947914_button_addons.js]
 skip-if = os == "linux" # Intermittent failures
 [browser_947914_button_copy.js]
+subsuite = clipboard
 skip-if = os == "linux" # Intermittent failures on Linux
 [browser_947914_button_cut.js]
+subsuite = clipboard
 skip-if = os == "linux" # Intermittent failures on Linux
 [browser_947914_button_find.js]
 skip-if = os == "linux" # Intermittent failures
 [browser_947914_button_history.js]
 skip-if = os == "linux" # Intermittent failures
 [browser_947914_button_newPrivateWindow.js]
 skip-if = os == "linux" # Intermittent failures
 [browser_947914_button_newWindow.js]
 skip-if = os == "linux" # Intermittent failures
 [browser_947914_button_paste.js]
+subsuite = clipboard
 skip-if = os == "linux" # Intermittent failures on Linux
 [browser_947914_button_print.js]
 skip-if = os == "linux" # Intermittent failures on Linux
 [browser_947914_button_savePage.js]
 skip-if = os == "linux" # Intermittent failures
 [browser_947914_button_zoomIn.js]
 skip-if = os == "linux" # Intermittent failures
 [browser_947914_button_zoomOut.js]
--- a/browser/components/extensions/test/browser/browser_ext_history.js
+++ b/browser/components/extensions/test/browser/browser_ext_history.js
@@ -104,38 +104,40 @@ add_task(function* test_delete() {
 });
 
 add_task(function* test_search() {
   const SINGLE_VISIT_URL = "http://example.com/";
   const DOUBLE_VISIT_URL = "http://example.com/2/";
   const MOZILLA_VISIT_URL = "http://mozilla.com/";
 
   function background() {
+    const futureTime = Date.now() + 24 * 60 * 60 * 1000;
+
     browser.test.onMessage.addListener(msg => {
       browser.history.search({text: ""}).then(results => {
         browser.test.sendMessage("empty-search", results);
         return browser.history.search({text: "mozilla.com"});
       }).then(results => {
         browser.test.sendMessage("text-search", results);
         return browser.history.search({text: "example.com", maxResults: 1});
       }).then(results => {
         browser.test.sendMessage("max-results-search", results);
-        return browser.history.search({text: "", startTime: Date.now()});
+        return browser.history.search({text: "", startTime: futureTime});
       }).then(results => {
         browser.test.assertEq(0, results.length, "no results returned for late start time");
         return browser.history.search({text: "", endTime: 0});
       }).then(results => {
         browser.test.assertEq(0, results.length, "no results returned for early end time");
         return browser.history.search({text: "", startTime: Date.now(), endTime: 0});
       }).then(results => {
         browser.test.fail("history.search rejects with startTime that is after the endTime");
       }, error => {
         browser.test.assertEq(
+          "The startTime cannot be after the endTime",
           error.message,
-          "The startTime cannot be after the endTime",
           "history.search rejects with startTime that is after the endTime");
       }).then(() => {
         browser.test.notifyPass("search");
       });
     });
 
     browser.test.sendMessage("ready");
   }
--- a/browser/components/places/tests/browser/browser.ini
+++ b/browser/components/places/tests/browser/browser.ini
@@ -9,20 +9,23 @@ support-files =
   framedPage.html
   frameLeft.html
   frameRight.html
   sidebarpanels_click_test_page.html
   keyword_form.html
 
 [browser_0_library_left_pane_migration.js]
 [browser_410196_paste_into_tags.js]
+subsuite = clipboard
 [browser_416459_cut.js]
+subsuite = clipboard
 [browser_423515.js]
 [browser_425884.js]
 [browser_435851_copy_query.js]
+subsuite = clipboard
 [browser_475045.js]
 [browser_555547.js]
 [browser_bookmarklet_windowOpen.js]
 support-files =
   pageopeningwindow.html
 [browser_bookmarkProperties_addFolderDefaultButton.js]
 [browser_bookmarkProperties_addKeywordForThisSearch.js]
 [browser_bookmarkProperties_addLivemark.js]
--- a/browser/modules/ContentLinkHandler.jsm
+++ b/browser/modules/ContentLinkHandler.jsm
@@ -160,51 +160,16 @@ this.ContentLinkHandler = {
           break;
       }
     }
   },
 
   getLinkIconURI: function(aLink) {
     let targetDoc = aLink.ownerDocument;
     var uri = BrowserUtils.makeURI(aLink.href, targetDoc.characterSet);
-
-    // Verify that the load of this icon is legal.
-    // Some error or special pages can load their favicon.
-    // To be on the safe side, only allow chrome:// favicons.
-    var isAllowedPage = [
-      /^about:neterror\?/,
-      /^about:blocked\?/,
-      /^about:certerror\?/,
-      /^about:home$/,
-    ].some(re => re.test(targetDoc.documentURI));
-
-    if (!isAllowedPage || !uri.schemeIs("chrome")) {
-      var ssm = Services.scriptSecurityManager;
-      try {
-        ssm.checkLoadURIWithPrincipal(targetDoc.nodePrincipal, uri,
-                                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
-      } catch(e) {
-        return null;
-      }
-    }
-
-    try {
-      var contentPolicy = Cc["@mozilla.org/layout/content-policy;1"].
-                          getService(Ci.nsIContentPolicy);
-    } catch(e) {
-      return null; // Refuse to load if we can't do a security check.
-    }
-
-    // Security says okay, now ask content policy
-    if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE,
-                                 uri, targetDoc.documentURIObject,
-                                 aLink, aLink.type, null)
-                                 != Ci.nsIContentPolicy.ACCEPT)
-      return null;
-
     try {
       uri.userPass = "";
     } catch(e) {
       // some URIs are immutable
     }
     return uri;
   },
 };
--- a/build/autoconf/android.m4
+++ b/build/autoconf/android.m4
@@ -202,50 +202,56 @@ AC_DEFUN([MOZ_ANDROID_AAR],[
 
   define([root], $MOZ_BUILD_ROOT/dist/exploded-aar/$1-$2/)
   MOZ_ANDROID_AAR_COMPONENT(concat(local_aar_var, _LIB), concat(root, $1-$2-classes.jar), REQUIRED)
   MOZ_ANDROID_AAR_COMPONENT(concat(local_aar_var, _RES), concat(root, res), REQUIRED)
   MOZ_ANDROID_AAR_COMPONENT(concat(local_aar_var, _INTERNAL_LIB), concat(root, libs/$1-$2-internal_impl-$2.jar), $5)
   MOZ_ANDROID_AAR_COMPONENT(concat(local_aar_var, _ASSETS), concat(root, assets), $6)
 ])
 
+ANDROID_SUPPORT_LIBRARY_VERSION="23.0.1"
+AC_SUBST(ANDROID_SUPPORT_LIBRARY_VERSION)
+
+ANDROID_GOOGLE_PLAY_SERVICES_VERSION="8.1.0"
+AC_SUBST(ANDROID_GOOGLE_PLAY_SERVICES_VERSION)
+
 AC_DEFUN([MOZ_ANDROID_GOOGLE_PLAY_SERVICES],
 [
 
 if test -n "$MOZ_NATIVE_DEVICES" ; then
     AC_SUBST(MOZ_NATIVE_DEVICES)
 
-    MOZ_ANDROID_AAR(play-services-base, 8.1.0, google, com/google/android/gms)
-    MOZ_ANDROID_AAR(play-services-basement, 8.1.0, google, com/google/android/gms)
-    MOZ_ANDROID_AAR(play-services-cast, 8.1.0, google, com/google/android/gms)
-    MOZ_ANDROID_AAR(mediarouter-v7, 23.0.1, android, com/android/support, REQUIRED_INTERNAL_IMPL)
+    MOZ_ANDROID_AAR(play-services-base, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms)
+    MOZ_ANDROID_AAR(play-services-basement, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms)
+    MOZ_ANDROID_AAR(play-services-cast, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms)
+    MOZ_ANDROID_AAR(mediarouter-v7, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support, REQUIRED_INTERNAL_IMPL)
 fi
 
 ])
 
 AC_DEFUN([MOZ_ANDROID_GOOGLE_CLOUD_MESSAGING],
 [
 
 if test -n "$MOZ_ANDROID_GCM" ; then
-    MOZ_ANDROID_AAR(play-services-base, 8.1.0, google, com/google/android/gms)
-    MOZ_ANDROID_AAR(play-services-basement, 8.1.0, google, com/google/android/gms)
-    MOZ_ANDROID_AAR(play-services-gcm, 8.1.0, google, com/google/android/gms)
+    MOZ_ANDROID_AAR(play-services-base, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms)
+    MOZ_ANDROID_AAR(play-services-basement, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms)
+    MOZ_ANDROID_AAR(play-services-gcm, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms)
 fi
 
 ])
 
 AC_DEFUN([MOZ_ANDROID_INSTALL_TRACKING],
 [
 
 if test -n "$MOZ_INSTALL_TRACKING"; then
     AC_SUBST(MOZ_INSTALL_TRACKING)
-    MOZ_ANDROID_AAR(play-services-ads, 8.1.0, google, com/google/android/gms)
-    MOZ_ANDROID_AAR(play-services-analytics, 8.1.0, google, com/google/android/gms)
-    MOZ_ANDROID_AAR(play-services-appindexing, 8.1.0, google, com/google/android/gms)
-    MOZ_ANDROID_AAR(play-services-basement, 8.1.0, google, com/google/android/gms)
+    MOZ_ANDROID_AAR(play-services-ads, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms)
+    MOZ_ANDROID_AAR(play-services-analytics, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms)
+    MOZ_ANDROID_AAR(play-services-appindexing, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms)
+    MOZ_ANDROID_AAR(play-services-basement, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms)
 fi
 
 ])
 
 dnl Configure an Android SDK.
 dnl Arg 1: target SDK version, like 22.
 dnl Arg 2: build tools version, like 22.0.1.
 AC_DEFUN([MOZ_ANDROID_SDK],
@@ -329,29 +335,31 @@ case "$target" in
     if test -z "$EMULATOR" -o "$EMULATOR" = ":"; then
       AC_MSG_ERROR([The program emulator was not found.  Try |mach bootstrap|.])
     fi
 
     ANDROID_TARGET_SDK="${android_target_sdk}"
     ANDROID_SDK="${android_sdk}"
     ANDROID_SDK_ROOT="${android_sdk_root}"
     ANDROID_TOOLS="${android_tools}"
+    ANDROID_BUILD_TOOLS_VERSION="$2"
     AC_DEFINE_UNQUOTED(ANDROID_TARGET_SDK,$ANDROID_TARGET_SDK)
     AC_SUBST(ANDROID_TARGET_SDK)
     AC_SUBST(ANDROID_SDK_ROOT)
     AC_SUBST(ANDROID_SDK)
     AC_SUBST(ANDROID_TOOLS)
+    AC_SUBST(ANDROID_BUILD_TOOLS_VERSION)
 
-    MOZ_ANDROID_AAR(appcompat-v7, 23.0.1, android, com/android/support)
-    MOZ_ANDROID_AAR(cardview-v7, 23.0.1, android, com/android/support)
-    MOZ_ANDROID_AAR(design, 23.0.1, android, com/android/support)
-    MOZ_ANDROID_AAR(recyclerview-v7, 23.0.1, android, com/android/support)
-    MOZ_ANDROID_AAR(support-v4, 23.0.1, android, com/android/support, REQUIRED_INTERNAL_IMPL)
+    MOZ_ANDROID_AAR(appcompat-v7, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support)
+    MOZ_ANDROID_AAR(cardview-v7, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support)
+    MOZ_ANDROID_AAR(design, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support)
+    MOZ_ANDROID_AAR(recyclerview-v7, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support)
+    MOZ_ANDROID_AAR(support-v4, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support, REQUIRED_INTERNAL_IMPL)
 
-    ANDROID_SUPPORT_ANNOTATIONS_JAR="$ANDROID_SDK_ROOT/extras/android/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.jar"
+    ANDROID_SUPPORT_ANNOTATIONS_JAR="$ANDROID_SDK_ROOT/extras/android/m2repository/com/android/support/support-annotations/$ANDROID_SUPPORT_LIBRARY_VERSION/support-annotations-$ANDROID_SUPPORT_LIBRARY_VERSION.jar"
     AC_MSG_CHECKING([for support-annotations JAR])
     if ! test -e $ANDROID_SUPPORT_ANNOTATIONS_JAR ; then
         AC_MSG_ERROR([You must download the support-annotations lib.  Run the Android SDK tool and install the Android Support Repository under Extras.  See https://developer.android.com/tools/extras/support-library.html for more info. (looked for $ANDROID_SUPPORT_ANNOTATIONS_JAR)])
     fi
     AC_MSG_RESULT([$ANDROID_SUPPORT_ANNOTATIONS_JAR])
     AC_SUBST(ANDROID_SUPPORT_ANNOTATIONS_JAR)
     ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB=$ANDROID_SUPPORT_ANNOTATIONS_JAR
     AC_SUBST(ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB)
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -448,23 +448,23 @@ class Automation(object):
           self.dumpScreen(utilityPath)
 
         (line, didTimeout) = self.readWithTimeout(logsource, timeout)
 
         if not hitMaxTime and maxTime and datetime.now() - startTime > timedelta(seconds = maxTime):
           # Kill the application.
           hitMaxTime = True
           self.log.info("TEST-UNEXPECTED-FAIL | %s | application ran for longer than allowed maximum time of %d seconds", self.lastTestSeen, int(maxTime))
-          self.log.warning("Force-terminating active process(es).");
+          self.log.error("Force-terminating active process(es).");
           self.killAndGetStack(proc.pid, utilityPath, debuggerInfo)
       if didTimeout:
         if line:
           self.log.info(line.rstrip().decode("UTF-8", "ignore"))
         self.log.info("TEST-UNEXPECTED-FAIL | %s | application timed out after %d seconds with no output", self.lastTestSeen, int(timeout))
-        self.log.warning("Force-terminating active process(es).");
+        self.log.error("Force-terminating active process(es).");
         if browserProcessId == -1:
           browserProcessId = proc.pid
         self.killAndGetStack(browserProcessId, utilityPath, debuggerInfo)
 
     status = proc.wait()
     printstatus("Main app process", status)
     if status == 0:
       self.lastTestSeen = "Main app process exited normally"
--- a/devtools/client/commandline/test/browser.ini
+++ b/devtools/client/commandline/test/browser.ini
@@ -78,16 +78,17 @@ support-files =
 [browser_cmd_paintflashing.js]
 [browser_cmd_pref1.js]
 [browser_cmd_pref2.js]
 [browser_cmd_pref3.js]
 [browser_cmd_qsa.js]
 [browser_cmd_restart.js]
 [browser_cmd_rulers.js]
 [browser_cmd_screenshot.js]
+subsuite = clipboard
 support-files =
   browser_cmd_screenshot.html
 [browser_cmd_settings.js]
 [browser_gcli_async.js]
 [browser_gcli_canon.js]
 [browser_gcli_cli1.js]
 [browser_gcli_cli2.js]
 [browser_gcli_completion1.js]
--- a/devtools/client/debugger/test/mochitest/browser.ini
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -453,23 +453,25 @@ skip-if = e10s && debug
 [browser_dbg_source-maps-02.js]
 skip-if = e10s && debug
 [browser_dbg_source-maps-03.js]
 skip-if = e10s && debug
 [browser_dbg_source-maps-04.js]
 skip-if = e10s # Bug 1093535
 [browser_dbg_sources-cache.js]
 [browser_dbg_sources-contextmenu-01.js]
+subsuite = clipboard
 [browser_dbg_sources-contextmenu-02.js]
 skip-if = e10s && debug
 [browser_dbg_sources-eval-01.js]
 skip-if = true # non-named eval sources turned off for now, bug 1124106
 [browser_dbg_sources-eval-02.js]
 [browser_dbg_sources-iframe-reload.js]
 [browser_dbg_sources-keybindings.js]
+subsuite = clipboard
 skip-if = e10s && debug
 [browser_dbg_sources-labels.js]
 skip-if = e10s && debug
 [browser_dbg_sources-large.js]
 [browser_dbg_sources-sorting.js]
 skip-if = e10s && debug
 [browser_dbg_sources-bookmarklet.js]
 skip-if = e10s && debug
@@ -488,16 +490,17 @@ skip-if = e10s && debug
 skip-if = e10s && (debug || asan) # timeouts
 [browser_dbg_stack-06.js]
 skip-if = e10s && debug
 [browser_dbg_stack-07.js]
 skip-if = e10s && debug
 [browser_dbg_stack-contextmenu-01.js]
 skip-if = e10s && debug
 [browser_dbg_stack-contextmenu-02.js]
+subsuite = clipboard
 skip-if = e10s && debug
 [browser_dbg_step-out.js]
 skip-if = e10s && debug
 [browser_dbg_tabactor-01.js]
 skip-if = e10s # TODO
 [browser_dbg_tabactor-02.js]
 skip-if = e10s # TODO
 [browser_dbg_terminate-on-tab-close.js]
@@ -510,16 +513,17 @@ skip-if = e10s && debug
 skip-if = e10s && debug
 [browser_dbg_variables-view-04.js]
 skip-if = e10s && debug
 [browser_dbg_variables-view-05.js]
 skip-if = e10s && debug
 [browser_dbg_variables-view-06.js]
 skip-if = e10s && debug
 [browser_dbg_variables-view-accessibility.js]
+subsuite = clipboard
 skip-if = e10s && debug
 [browser_dbg_variables-view-data.js]
 skip-if = e10s && debug
 [browser_dbg_variables-view-edit-cancel.js]
 skip-if = e10s && debug
 [browser_dbg_variables-view-edit-click.js]
 skip-if = e10s || (os == 'mac' || os == 'win') && (debug == false) # Bug 986166
 [browser_dbg_variables-view-edit-getset-01.js]
--- a/devtools/client/devtools-startup.manifest
+++ b/devtools/client/devtools-startup.manifest
@@ -1,2 +1,8 @@
 component {9e9a9283-0ce9-4e4a-8f1c-ba129a032c32} devtools-startup.js
-contract @mozilla.org/toolkit/console-clh;1 {9e9a9283-0ce9-4e4a-8f1c-ba129a032c32}
+contract @mozilla.org/devtools/startup-clh;1 {9e9a9283-0ce9-4e4a-8f1c-ba129a032c32}
+# We want this to override toolkit's --jsconsole handling, so it must have a
+# a higher priority than the entry in jsconsole-clhandler.manifest.  Higher
+# priority means the "m-devtools" value below needs to be something that sorts
+# before the one in jsconsole-clhandler.manifest.  See details in
+# nsICommandLineHandler.idl.
+category command-line-handler m-devtools @mozilla.org/devtools/startup-clh;1
--- a/devtools/client/eyedropper/test/browser.ini
+++ b/devtools/client/eyedropper/test/browser.ini
@@ -1,11 +1,11 @@
 [DEFAULT]
 tags = devtools
-subsuite = devtools
+subsuite = clipboard
 support-files =
   color-block.html
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/framework/test/shared-head.js
 
 [browser_eyedropper_basic.js]
 skip-if = os == "win" && debug # bug 963492
--- a/devtools/client/framework/test/browser_keybindings_02.js
+++ b/devtools/client/framework/test/browser_keybindings_02.js
@@ -5,16 +5,18 @@
 
 "use strict";
 
 // Test that the toolbox keybindings still work after the host is changed.
 
 const URL = "data:text/html;charset=utf8,test page";
 
 var {Toolbox} = require("devtools/client/framework/toolbox");
+var strings = Services.strings.createBundle(
+  "chrome://devtools/locale/toolbox.properties");
 
 add_task(function* () {
   info("Create a test tab and open the toolbox");
   let tab = yield addTab(URL);
   let target = TargetFactory.forTab(tab);
   let toolbox = yield gDevTools.showToolbox(target, "webconsole");
 
   let {SIDE, BOTTOM} = Toolbox.HostType;
@@ -28,31 +30,31 @@ add_task(function* () {
 
   Services.prefs.clearUserPref("devtools.toolbox.zoomValue");
   Services.prefs.setCharPref("devtools.toolbox.host", BOTTOM);
   yield toolbox.destroy();
   gBrowser.removeCurrentTab();
 });
 
 function zoomWithKey(toolbox, key) {
-  if (!key) {
+  let shortcut = strings.GetStringFromName(key);
+  if (!shortcut) {
     info("Key was empty, skipping zoomWithKey");
     return;
   }
-
   info("Zooming with key: " + key);
   let currentZoom = toolbox.zoomValue;
-  EventUtils.synthesizeKey(key, {accelKey: true}, toolbox.win);
+  synthesizeKeyShortcut(shortcut);
   isnot(toolbox.zoomValue, currentZoom, "The zoom level was changed in the toolbox");
 }
 
 function* checkKeyBindings(toolbox) {
-  zoomWithKey(toolbox, toolbox.doc.getElementById("toolbox-zoom-in-key").getAttribute("key"));
-  zoomWithKey(toolbox, toolbox.doc.getElementById("toolbox-zoom-in-key2").getAttribute("key"));
-  zoomWithKey(toolbox, toolbox.doc.getElementById("toolbox-zoom-in-key3").getAttribute("key"));
+  zoomWithKey(toolbox, "toolbox.zoomIn.key");
+  zoomWithKey(toolbox, "toolbox.zoomIn2.key");
+  zoomWithKey(toolbox, "toolbox.zoomIn3.key");
 
-  zoomWithKey(toolbox, toolbox.doc.getElementById("toolbox-zoom-reset-key").getAttribute("key"));
+  zoomWithKey(toolbox, "toolbox.zoomReset.key");
 
-  zoomWithKey(toolbox, toolbox.doc.getElementById("toolbox-zoom-out-key").getAttribute("key"));
-  zoomWithKey(toolbox, toolbox.doc.getElementById("toolbox-zoom-out-key2").getAttribute("key"));
+  zoomWithKey(toolbox, "toolbox.zoomOut.key");
+  zoomWithKey(toolbox, "toolbox.zoomOut2.key");
 
-  zoomWithKey(toolbox, toolbox.doc.getElementById("toolbox-zoom-reset-key2").getAttribute("key"));
+  zoomWithKey(toolbox, "toolbox.zoomReset2.key");
 }
--- a/devtools/client/framework/test/browser_keybindings_03.js
+++ b/devtools/client/framework/test/browser_keybindings_03.js
@@ -6,42 +6,47 @@
 "use strict";
 
 // Test that the toolbox 'switch to previous host' feature works.
 // Pressing ctrl/cmd+shift+d should switch to the last used host.
 
 const URL = "data:text/html;charset=utf8,test page for toolbox switching";
 
 var {Toolbox} = require("devtools/client/framework/toolbox");
+var strings = Services.strings.createBundle(
+  "chrome://devtools/locale/toolbox.properties");
 
 add_task(function* () {
   info("Create a test tab and open the toolbox");
   let tab = yield addTab(URL);
   let target = TargetFactory.forTab(tab);
   let toolbox = yield gDevTools.showToolbox(target, "webconsole");
 
-  let keyElement = toolbox.doc.getElementById("toolbox-toggle-host-key");
+  let shortcut = strings.GetStringFromName("toolbox.toggleHost.key");
 
   let {SIDE, BOTTOM, WINDOW} = Toolbox.HostType;
   checkHostType(toolbox, BOTTOM, SIDE);
 
   info("Switching from bottom to side");
-  synthesizeKeyElement(keyElement);
-  yield toolbox.once("host-changed");
+  let onHostChanged = toolbox.once("host-changed");
+  synthesizeKeyShortcut(shortcut, toolbox.win);
+  yield onHostChanged;
   checkHostType(toolbox, SIDE, BOTTOM);
 
   info("Switching from side to bottom");
-  synthesizeKeyElement(keyElement);
-  yield toolbox.once("host-changed");
+  onHostChanged = toolbox.once("host-changed");
+  synthesizeKeyShortcut(shortcut, toolbox.win);
+  yield onHostChanged;
   checkHostType(toolbox, BOTTOM, SIDE);
 
   info("Switching to window");
   yield toolbox.switchHost(WINDOW);
   checkHostType(toolbox, WINDOW, BOTTOM);
 
   info("Switching from window to bottom");
-  synthesizeKeyElement(keyElement);
-  yield toolbox.once("host-changed");
+  onHostChanged = toolbox.once("host-changed");
+  synthesizeKeyShortcut(shortcut, toolbox.win);
+  yield onHostChanged;
   checkHostType(toolbox, BOTTOM, WINDOW);
 
   yield toolbox.destroy();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/client/framework/test/browser_toolbox_options.js
+++ b/devtools/client/framework/test/browser_toolbox_options.js
@@ -5,16 +5,18 @@
 
 /* import-globals-from shared-head.js */
 "use strict";
 
 // Tests that changing preferences in the options panel updates the prefs
 // and toggles appropriate things in the toolbox.
 
 var doc = null, toolbox = null, panelWin = null, modifiedPrefs = [];
+var strings = Services.strings.createBundle(
+  "chrome://devtools/locale/toolbox.properties");
 
 add_task(function* () {
   const URL = "data:text/html;charset=utf8,test for dynamically registering " +
               "and unregistering tools";
   registerNewTool();
   let tab = yield addTab(URL);
   let target = TargetFactory.forTab(tab);
   toolbox = yield gDevTools.showToolbox(target);
@@ -53,28 +55,28 @@ function* testSelectTool() {
   ok(true, "Toolbox selected via selectTool method");
 }
 
 function* testOptionsShortcut() {
   info("Selecting another tool, then reselecting options panel with keyboard.");
 
   yield toolbox.selectTool("webconsole");
   is(toolbox.currentToolId, "webconsole", "webconsole is selected");
-  synthesizeKeyFromKeyTag(doc.getElementById("toolbox-options-key"));
+  synthesizeKeyShortcut(strings.GetStringFromName("toolbox.options.key"));
   is(toolbox.currentToolId, "options", "Toolbox selected via shortcut key (1)");
-  synthesizeKeyFromKeyTag(doc.getElementById("toolbox-options-key"));
+  synthesizeKeyShortcut(strings.GetStringFromName("toolbox.options.key"));
   is(toolbox.currentToolId, "webconsole", "webconsole is selected (1)");
 
   yield toolbox.selectTool("webconsole");
   is(toolbox.currentToolId, "webconsole", "webconsole is selected");
-  synthesizeKeyFromKeyTag(doc.getElementById("toolbox-options-key2"));
+  synthesizeKeyShortcut(strings.GetStringFromName("toolbox.help.key"));
   is(toolbox.currentToolId, "options", "Toolbox selected via shortcut key (2)");
-  synthesizeKeyFromKeyTag(doc.getElementById("toolbox-options-key"));
+  synthesizeKeyShortcut(strings.GetStringFromName("toolbox.options.key"));
   is(toolbox.currentToolId, "webconsole", "webconsole is reselected (2)");
-  synthesizeKeyFromKeyTag(doc.getElementById("toolbox-options-key2"));
+  synthesizeKeyShortcut(strings.GetStringFromName("toolbox.help.key"));
   is(toolbox.currentToolId, "options", "Toolbox selected via shortcut key (2)");
 }
 
 function* testOptions() {
   let tool = toolbox.getPanel("options");
   panelWin = tool.panelWin;
   let prefNodes = tool.panelDoc.querySelectorAll(
     "input[type=checkbox][data-pref]");
--- a/devtools/client/framework/test/browser_toolbox_tabsswitch_shortcuts.js
+++ b/devtools/client/framework/test/browser_toolbox_tabsswitch_shortcuts.js
@@ -1,66 +1,66 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 requestLongerTimeout(2);
 
 var {Toolbox} = require("devtools/client/framework/toolbox");
+var strings = Services.strings.createBundle(
+  "chrome://devtools/locale/toolbox.properties");
 
 add_task(function* () {
   let tab = yield addTab("about:blank");
   let target = TargetFactory.forTab(tab);
   yield target.makeRemote();
 
   let toolIDs = gDevTools.getToolDefinitionArray()
                          .filter(def => def.isTargetSupported(target))
                          .map(def => def.id);
 
   let toolbox = yield gDevTools.showToolbox(target, toolIDs[0],
                                             Toolbox.HostType.BOTTOM);
-  let nextKey = toolbox.doc.getElementById("toolbox-next-tool-key")
-                           .getAttribute("key");
-  let prevKey = toolbox.doc.getElementById("toolbox-previous-tool-key")
-                           .getAttribute("key");
+  let nextShortcut = strings.GetStringFromName("toolbox.nextTool.key")
+  let prevShortcut = strings.GetStringFromName("toolbox.previousTool.key")
 
   // Iterate over all tools, starting from options to netmonitor, in normal
   // order.
   for (let i = 1; i < toolIDs.length; i++) {
-    yield testShortcuts(toolbox, i, nextKey, toolIDs);
+    yield testShortcuts(toolbox, i, nextShortcut, toolIDs);
   }
 
   // Iterate again, in the same order, starting from netmonitor (so next one is
   // 0: options).
   for (let i = 0; i < toolIDs.length; i++) {
-    yield testShortcuts(toolbox, i, nextKey, toolIDs);
+    yield testShortcuts(toolbox, i, nextShortcut, toolIDs);
   }
 
   // Iterate over all tools in reverse order, starting from netmonitor to
   // options.
   for (let i = toolIDs.length - 2; i >= 0; i--) {
-    yield testShortcuts(toolbox, i, prevKey, toolIDs);
+    yield testShortcuts(toolbox, i, prevShortcut, toolIDs);
   }
 
   // Iterate again, in reverse order again, starting from options (so next one
   // is length-1: netmonitor).
   for (let i = toolIDs.length - 1; i >= 0; i--) {
-    yield testShortcuts(toolbox, i, prevKey, toolIDs);
+    yield testShortcuts(toolbox, i, prevShortcut, toolIDs);
   }
 
   yield toolbox.destroy();
   gBrowser.removeCurrentTab();
 });
 
-function* testShortcuts(toolbox, index, key, toolIDs) {
+function* testShortcuts(toolbox, index, shortcut, toolIDs) {
   info("Testing shortcut to switch to tool " + index + ":" + toolIDs[index] +
-       " using key " + key);
+       " using shortcut " + shortcut);
 
   let onToolSelected = toolbox.once("select");
-  EventUtils.synthesizeKey(key, {accelKey: true}, toolbox.win);
+  synthesizeKeyShortcut(shortcut);
   let id = yield onToolSelected;
 
   info("toolbox-select event from " + id);
 
   is(toolIDs.indexOf(id), index,
      "Correct tool is selected on pressing the shortcut for " + id);
 }
--- a/devtools/client/framework/test/browser_toolbox_window_reload_target.js
+++ b/devtools/client/framework/test/browser_toolbox_window_reload_target.js
@@ -5,16 +5,18 @@
 
 requestLongerTimeout(10);
 
 const TEST_URL = "data:text/html;charset=utf-8," +
                  "<html><head><title>Test reload</title></head>" +
                  "<body><h1>Testing reload from devtools</h1></body></html>";
 
 var {Toolbox} = require("devtools/client/framework/toolbox");
+var strings = Services.strings.createBundle(
+  "chrome://devtools/locale/toolbox.properties");
 
 var target, toolbox, description, reloadsSent, toolIDs;
 
 function test() {
   addTab(TEST_URL).then(() => {
     target = TargetFactory.forTab(gBrowser.selectedTab);
 
     target.makeRemote().then(() => {
@@ -55,39 +57,38 @@ function startReloadTest(aToolbox) {
   }, toolIDs.length - 1 /* only test 1 tool in docked mode, to cut down test time */);
 }
 
 function testAllTheTools(docked, callback, toolNum = 0) {
   if (toolNum >= toolIDs.length) {
     return callback();
   }
   toolbox.selectTool(toolIDs[toolNum]).then(() => {
-    testReload("toolbox-reload-key", docked, toolIDs[toolNum], () => {
-      testReload("toolbox-reload-key2", docked, toolIDs[toolNum], () => {
-        testReload("toolbox-force-reload-key", docked, toolIDs[toolNum], () => {
-          testReload("toolbox-force-reload-key2", docked, toolIDs[toolNum], () => {
+    testReload("toolbox.reload.key", docked, toolIDs[toolNum], () => {
+      testReload("toolbox.reload2.key", docked, toolIDs[toolNum], () => {
+        testReload("toolbox.forceReload.key", docked, toolIDs[toolNum], () => {
+          testReload("toolbox.forceReload2.key", docked, toolIDs[toolNum], () => {
             testAllTheTools(docked, callback, toolNum + 1);
           });
         });
       });
     });
   });
 }
 
-function testReload(key, docked, toolID, callback) {
+function testReload(shortcut, docked, toolID, callback) {
   let complete = () => {
     gBrowser.selectedBrowser.messageManager.removeMessageListener("devtools:test:load", complete);
     return callback();
   };
   gBrowser.selectedBrowser.messageManager.addMessageListener("devtools:test:load", complete);
 
-  description = docked + " devtools with tool " + toolID + ", key #" + key;
+  description = docked + " devtools with tool " + toolID + ", shortcut #" + shortcut;
   info("Testing reload in " + description);
-  let el = toolbox.doc.getElementById(key);
-  synthesizeKeyElement(el);
+  synthesizeKeyShortcut(strings.GetStringFromName(shortcut), toolbox.win);
   reloadsSent++;
 }
 
 function finishUp() {
   toolbox.destroy().then(() => {
     gBrowser.removeCurrentTab();
 
     target = toolbox = description = reloadsSent = toolIDs = null;
--- a/devtools/client/framework/test/browser_toolbox_zoom.js
+++ b/devtools/client/framework/test/browser_toolbox_zoom.js
@@ -3,16 +3,18 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 var modifiers = {
   accelKey: true
 };
 
 var toolbox;
+var strings = Services.strings.createBundle(
+  "chrome://devtools/locale/toolbox.properties");
 
 function test() {
   addTab("about:blank").then(openToolbox);
 }
 
 function openToolbox() {
   let target = TargetFactory.forTab(gBrowser.selectedTab);
 
@@ -20,37 +22,36 @@ function openToolbox() {
     toolbox = aToolbox;
     toolbox.selectTool("styleeditor").then(testZoom);
   });
 }
 
 function testZoom() {
   info("testing zoom keys");
 
-  testZoomLevel("in", 2, 1.2);
-  testZoomLevel("out", 3, 0.9);
-  testZoomLevel("reset", 1, 1);
+  testZoomLevel("In", 2, 1.2);
+  testZoomLevel("Out", 3, 0.9);
+  testZoomLevel("Reset", 1, 1);
 
   tidyUp();
 }
 
 function testZoomLevel(type, times, expected) {
-  sendZoomKey("toolbox-zoom-" + type + "-key", times);
+  sendZoomKey("toolbox.zoom" + type + ".key", times);
 
   let zoom = getCurrentZoom(toolbox);
   is(zoom.toFixed(2), expected, "zoom level correct after zoom " + type);
 
   is(toolbox.zoomValue.toFixed(2), expected,
      "saved zoom level is correct after zoom " + type);
 }
 
-function sendZoomKey(id, times) {
-  let key = toolbox.doc.getElementById(id).getAttribute("key");
+function sendZoomKey(shortcut, times) {
   for (let i = 0; i < times; i++) {
-    EventUtils.synthesizeKey(key, modifiers, toolbox.win);
+    synthesizeKeyShortcut(strings.GetStringFromName(shortcut));
   }
 }
 
 function getCurrentZoom() {
   var contViewer = toolbox.frame.docShell.contentViewer;
   return contViewer.fullZoom;
 }
 
--- a/devtools/client/framework/test/shared-head.js
+++ b/devtools/client/framework/test/shared-head.js
@@ -163,30 +163,32 @@ function synthesizeKeyFromKeyTag(key) {
   EventUtils.synthesizeKey(name, modifiers);
 }
 
 /**
  * Simulate a key event from an electron key shortcut string:
  * https://github.com/electron/electron/blob/master/docs/api/accelerator.md
  *
  * @param {String} key
+ * @param {DOMWindow} target
+ *        Optional window where to fire the key event
  */
-function synthesizeKeyShortcut(key) {
+function synthesizeKeyShortcut(key, target) {
   // parseElectronKey requires any window, just to access `KeyboardEvent`
   let window = Services.appShell.hiddenDOMWindow;
   let shortcut = KeyShortcuts.parseElectronKey(window, key);
 
   info("Synthesizing key shortcut: " + key);
   EventUtils.synthesizeKey(shortcut.key || "", {
     keyCode: shortcut.keyCode,
     altKey: shortcut.alt,
     ctrlKey: shortcut.ctrl,
     metaKey: shortcut.meta,
     shiftKey: shortcut.shift
-  });
+  }, target);
 }
 
 /**
  * Wait for eventName on target to be delivered a number of times.
  *
  * @param {Object} target
  *        An observable object that either supports on/off or
  *        addEventListener/removeEventListener
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -63,16 +63,19 @@ loader.lazyRequireGetter(this, "DevTools
 loader.lazyRequireGetter(this, "showDoorhanger",
   "devtools/client/shared/doorhanger", true);
 loader.lazyRequireGetter(this, "createPerformanceFront",
   "devtools/server/actors/performance", true);
 loader.lazyRequireGetter(this, "system",
   "devtools/shared/system");
 loader.lazyRequireGetter(this, "getPreferenceFront",
   "devtools/server/actors/preference", true);
+loader.lazyRequireGetter(this, "KeyShortcuts",
+  "devtools/client/shared/key-shortcuts", true);
+
 loader.lazyGetter(this, "osString", () => {
   return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
 });
 loader.lazyGetter(this, "registerHarOverlay", () => {
   return require("devtools/client/netmonitor/har/toolbox-overlay").register;
 });
 
 // White-list buttons that can be toggled to prevent adding prefs for
@@ -412,29 +415,30 @@ Toolbox.prototype = {
       let noautohideMenu = this.doc.getElementById("command-button-noautohide");
       noautohideMenu.addEventListener("command", this._toggleAutohide, true);
 
       this.textboxContextMenuPopup =
         this.doc.getElementById("toolbox-textbox-context-popup");
       this.textboxContextMenuPopup.addEventListener("popupshowing",
         this._updateTextboxMenuItems, true);
 
+      var shortcuts = new KeyShortcuts({
+        window: this.doc.defaultView
+      });
       this._buildDockButtons();
-      this._buildOptions();
+      this._buildOptions(shortcuts);
       this._buildTabs();
       this._applyCacheSettings();
       this._applyServiceWorkersTestingSettings();
       this._addKeysToWindow();
-      this._addReloadKeys();
-      this._addHostListeners();
+      this._addReloadKeys(shortcuts);
+      this._addHostListeners(shortcuts);
       this._registerOverlays();
-      if (this._hostOptions && this._hostOptions.zoom === false) {
-        this._disableZoomKeys();
-      } else {
-        this._addZoomKeys();
+      if (!this._hostOptions || this._hostOptions.zoom === true) {
+        this._addZoomKeys(shortcuts);
         this._loadInitialZoom();
       }
       this._setToolbarKeyboardNavigation();
 
       this.webconsolePanel = this.doc.querySelector("#toolbox-panel-webconsole");
       this.webconsolePanel.height = Services.prefs.getIntPref(SPLITCONSOLE_HEIGHT_PREF);
       this.webconsolePanel.addEventListener("resize", this._saveSplitConsoleHeight);
 
@@ -518,44 +522,45 @@ Toolbox.prototype = {
         this._applyCacheSettings();
         break;
       case "devtools.serviceWorkers.testing.enabled":
         this._applyServiceWorkersTestingSettings();
         break;
     }
   },
 
-  _buildOptions: function () {
-    let selectOptions = () => {
+  _buildOptions: function (shortcuts) {
+    let selectOptions = (name, event) => {
       // Flip back to the last used panel if we are already
       // on the options panel.
       if (this.currentToolId === "options" &&
           gDevTools.getToolDefinition(this.lastUsedToolId)) {
         this.selectTool(this.lastUsedToolId);
       } else {
         this.selectTool("options");
       }
+      // Prevent the opening of bookmarks window on toolbox.options.key
+      event.preventDefault();
     };
-    let key = this.doc.getElementById("toolbox-options-key");
-    key.addEventListener("command", selectOptions, true);
-    let key2 = this.doc.getElementById("toolbox-options-key2");
-    key2.addEventListener("command", selectOptions, true);
+    shortcuts.on(toolboxStrings("toolbox.options.key"), selectOptions);
+    shortcuts.on(toolboxStrings("toolbox.help.key"), selectOptions);
   },
 
   _splitConsoleOnKeypress: function (e) {
     if (e.keyCode === e.DOM_VK_ESCAPE) {
       this.toggleSplitConsole();
       // If the debugger is paused, don't let the ESC key stop any pending
       // navigation.
       let jsdebugger = this.getPanel("jsdebugger");
       if (jsdebugger && jsdebugger.panelWin.gThreadClient.state == "paused") {
         e.preventDefault();
       }
     }
   },
+
   /**
    * Add a shortcut key that should work when a split console
    * has focus to the toolbox.
    *
    * @param {element} keyElement
    *        They <key> XUL element describing the shortcut key
    * @param {string} whichTool
    *        The tool the key belongs to. The corresponding command
@@ -569,44 +574,41 @@ Toolbox.prototype = {
       // Only forward the command if the tool is active
       if (this.currentToolId === whichTool && this.isSplitConsoleFocused()) {
         keyElement.doCommand();
       }
     }, true);
     this.doc.getElementById("toolbox-keyset").appendChild(cloned);
   },
 
-  _addReloadKeys: function () {
+  _addReloadKeys: function (shortcuts) {
     [
-      ["toolbox-reload-key", false],
-      ["toolbox-reload-key2", false],
-      ["toolbox-force-reload-key", true],
-      ["toolbox-force-reload-key2", true]
+      ["reload", false],
+      ["reload2", false],
+      ["forceReload", true],
+      ["forceReload2", true]
     ].forEach(([id, force]) => {
-      this.doc.getElementById(id).addEventListener("command", () => {
-        this.reloadTarget(force);
-      }, true);
+      let key = toolboxStrings("toolbox." + id + ".key");
+      shortcuts.on(key, this.reloadTarget.bind(this, force));
     });
   },
 
-  _addHostListeners: function () {
-    let nextKey = this.doc.getElementById("toolbox-next-tool-key");
-    nextKey.addEventListener("command", this.selectNextTool.bind(this), true);
-
-    let prevKey = this.doc.getElementById("toolbox-previous-tool-key");
-    prevKey.addEventListener("command", this.selectPreviousTool.bind(this), true);
+  _addHostListeners: function (shortcuts) {
+    shortcuts.on(toolboxStrings("toolbox.nextTool.key"),
+                 this.selectNextTool.bind(this));
+    shortcuts.on(toolboxStrings("toolbox.previousTool.key"),
+                 this.selectPreviousTool.bind(this));
+    shortcuts.on(toolboxStrings("toolbox.minimize.key"),
+                 this._toggleMinimizeMode.bind(this));
+    shortcuts.on(toolboxStrings("toolbox.toggleHost.key"),
+                 (name, event) => {
+                   this.switchToPreviousHost();
+                   event.preventDefault();
+                 });
 
-    let minimizeKey = this.doc.getElementById("toolbox-minimize-key");
-    minimizeKey.addEventListener("command", this._toggleMinimizeMode, true);
-
-    let toggleKey = this.doc.getElementById("toolbox-toggle-host-key");
-    toggleKey.addEventListener("command", this.switchToPreviousHost.bind(this), true);
-
-    // Split console uses keypress instead of command so the event can be
-    // cancelled with stopPropagation on the keypress, and not preventDefault.
     this.doc.addEventListener("keypress", this._splitConsoleOnKeypress, false);
 
     this.doc.addEventListener("focus", this._onFocus, true);
   },
 
   _registerOverlays: function () {
     registerHarOverlay(this);
   },
@@ -648,88 +650,72 @@ Toolbox.prototype = {
         splitter.setAttribute("hidden", "true");
       }
     }
   },
 
   /**
    * Wire up the listeners for the zoom keys.
    */
-  _addZoomKeys: function () {
-    let inKey = this.doc.getElementById("toolbox-zoom-in-key");
-    inKey.addEventListener("command", this.zoomIn.bind(this), true);
-
-    let inKey2 = this.doc.getElementById("toolbox-zoom-in-key2");
-    inKey2.addEventListener("command", this.zoomIn.bind(this), true);
-
-    let inKey3 = this.doc.getElementById("toolbox-zoom-in-key3");
-    inKey3.addEventListener("command", this.zoomIn.bind(this), true);
-
-    let outKey = this.doc.getElementById("toolbox-zoom-out-key");
-    outKey.addEventListener("command", this.zoomOut.bind(this), true);
-
-    let outKey2 = this.doc.getElementById("toolbox-zoom-out-key2");
-    outKey2.addEventListener("command", this.zoomOut.bind(this), true);
-
-    let resetKey = this.doc.getElementById("toolbox-zoom-reset-key");
-    resetKey.addEventListener("command", this.zoomReset.bind(this), true);
+  _addZoomKeys: function (shortcuts) {
+    shortcuts.on(toolboxStrings("toolbox.zoomIn.key"),
+                 this.zoomIn.bind(this));
+    let zoomIn2 = toolboxStrings("toolbox.zoomIn2.key");
+    if (zoomIn2) {
+      shortcuts.on(zoomIn2, this.zoomIn.bind(this));
+    }
+    let zoomIn3 = toolboxStrings("toolbox.zoomIn2.key");
+    if (zoomIn3) {
+      shortcuts.on(zoomIn3, this.zoomIn.bind(this));
+    }
 
-    let resetKey2 = this.doc.getElementById("toolbox-zoom-reset-key2");
-    resetKey2.addEventListener("command", this.zoomReset.bind(this), true);
-  },
-
-  _disableZoomKeys: function () {
-    let inKey = this.doc.getElementById("toolbox-zoom-in-key");
-    inKey.setAttribute("disabled", "true");
-
-    let inKey2 = this.doc.getElementById("toolbox-zoom-in-key2");
-    inKey2.setAttribute("disabled", "true");
+    shortcuts.on(toolboxStrings("toolbox.zoomOut.key"),
+                 this.zoomOut.bind(this));
+    let zoomOut2 = toolboxStrings("toolbox.zoomOut2.key");
+    if (zoomOut2) {
+      shortcuts.on(zoomOut2, this.zoomOut.bind(this));
+    }
 
-    let inKey3 = this.doc.getElementById("toolbox-zoom-in-key3");
-    inKey3.setAttribute("disabled", "true");
-
-    let outKey = this.doc.getElementById("toolbox-zoom-out-key");
-    outKey.setAttribute("disabled", "true");
-
-    let outKey2 = this.doc.getElementById("toolbox-zoom-out-key2");
-    outKey2.setAttribute("disabled", "true");
-
-    let resetKey = this.doc.getElementById("toolbox-zoom-reset-key");
-    resetKey.setAttribute("disabled", "true");
-
-    let resetKey2 = this.doc.getElementById("toolbox-zoom-reset-key2");
-    resetKey2.setAttribute("disabled", "true");
+    shortcuts.on(toolboxStrings("toolbox.zoomReset.key"),
+                 this.zoomReset.bind(this));
+    let zoomReset2 = toolboxStrings("toolbox.zoomReset2.key");
+    if (zoomReset2) {
+      shortcuts.on(zoomReset2, this.zoomReset.bind(this));
+    }
   },
 
   /**
    * Set zoom on toolbox to whatever the last setting was.
    */
   _loadInitialZoom: function () {
     this.setZoom(this.zoomValue);
   },
 
   /**
    * Increase zoom level of toolbox window - make things bigger.
    */
-  zoomIn: function () {
+  zoomIn: function (name, event) {
     this.setZoom(this.zoomValue + 0.1);
+    event.preventDefault();
   },
 
   /**
    * Decrease zoom level of toolbox window - make things smaller.
    */
-  zoomOut: function () {
+  zoomOut: function (name, event) {
     this.setZoom(this.zoomValue - 0.1);
+    event.preventDefault();
   },
 
   /**
    * Reset zoom level of the toolbox window.
    */
-  zoomReset: function () {
+  zoomReset: function (name, event) {
     this.setZoom(1);
+    event.preventDefault();
   },
 
   /**
    * Set zoom level of the toolbox window.
    *
    * @param {number} zoomValue
    *        Zoom level e.g. 1.2
    */
@@ -895,20 +881,19 @@ Toolbox.prototype = {
         this.switchHost(position);
       });
 
       dockBox.appendChild(button);
     }
   },
 
   _getMinimizeButtonShortcutTooltip: function () {
-    let key = this.doc.getElementById("toolbox-minimize-key")
-                      .getAttribute("key");
-    return "(" + (osString == "Darwin" ? "Cmd+Shift+" : "Ctrl+Shift+") +
-           key.toUpperCase() + ")";
+    let str = toolboxStrings("toolbox.minimize.key");
+    let key = KeyShortcuts.parseElectronKey(this.win, str);
+    return "(" + KeyShortcuts.stringify(key) + ")";
   },
 
   _onBottomHostMinimized: function () {
     let btn = this.doc.querySelector("#toolbox-dock-bottom-minimize");
     btn.className = "minimized";
 
     btn.setAttribute("tooltiptext",
       toolboxStrings("toolboxDockButtons.bottom.maximize") + " " +
--- a/devtools/client/framework/toolbox.xul
+++ b/devtools/client/framework/toolbox.xul
@@ -24,85 +24,17 @@
           src="chrome://global/content/viewSourceUtils.js"/>
 
   <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
   <script type="application/javascript;version=1.8"
           src="chrome://devtools/content/framework/toolbox-init.js"/>
 
   <commandset id="editMenuCommands"/>
   <keyset id="editMenuKeys"/>
-  <keyset id="toolbox-keyset">
-    <key id="toolbox-options-key"
-         key="&toolboxOptionsButton.key;"
-         oncommand="void(0);"
-         modifiers="shift, accel"/>
-    <key id="toolbox-options-key2"
-         keycode="&openHelp.commandkey;"
-         oncommand="void(0);"/>
-    <key id="toolbox-next-tool-key"
-         key="&toolboxNextTool.key;"
-         oncommand="void(0);"
-         modifiers="accel"/>
-    <key id="toolbox-previous-tool-key"
-         key="&toolboxPreviousTool.key;"
-         oncommand="void(0);"
-         modifiers="accel"/>
-    <key id="toolbox-zoom-in-key"
-         key="&fullZoomEnlargeCmd.commandkey;"
-         oncommand="void(0);"
-         modifiers="accel"/>
-    <key id="toolbox-zoom-in-key2"
-         key="&fullZoomEnlargeCmd.commandkey2;"
-         oncommand="void(0);"
-         modifiers="accel"/>
-    <key id="toolbox-zoom-in-key3"
-         key="&fullZoomEnlargeCmd.commandkey3;"
-         oncommand="void(0);"
-         modifiers="accel"/>
-    <key id="toolbox-zoom-out-key"
-         key="&fullZoomReduceCmd.commandkey;"
-         oncommand="void(0);"
-         modifiers="accel"/>
-    <key id="toolbox-zoom-out-key2"
-         key="&fullZoomReduceCmd.commandkey2;"
-         oncommand="void(0);"
-         modifiers="accel"/>
-    <key id="toolbox-zoom-reset-key"
-         key="&fullZoomResetCmd.commandkey;"
-         oncommand="void(0);"
-         modifiers="accel"/>
-    <key id="toolbox-zoom-reset-key2"
-         key="&fullZoomResetCmd.commandkey2;"
-         oncommand="void(0);"
-         modifiers="accel"/>
-    <key id="toolbox-reload-key"
-         key="&toolboxReload.key;"
-         oncommand="void(0);"
-         modifiers="accel"/>
-    <key id="toolbox-force-reload-key"
-         key="&toolboxReload.key;"
-         oncommand="void(0);"
-         modifiers="accel shift"/>
-    <key id="toolbox-reload-key2"
-         keycode="VK_F5"
-         oncommand="void(0);"
-         modifiers=""/>
-    <key id="toolbox-force-reload-key2"
-         keycode="VK_F5"
-         oncommand="void(0);"
-         modifiers="accel"/>
-    <key id="toolbox-minimize-key"
-         key="&toolboxToggleMinimize.key;"
-         oncommand="void(0);"
-         modifiers="shift, accel"/>
-    <key id="toolbox-toggle-host-key"
-         key="&toolboxToggle.key;"
-         oncommand="void(0);"
-         modifiers="accel shift"/>
-  </keyset>
+  <keyset id="toolbox-keyset"/>
 
   <popupset>
     <menupopup id="toolbox-textbox-context-popup">
       <menuitem id="cMenu_undo"/>
       <menuseparator/>
       <menuitem id="cMenu_cut"/>
       <menuitem id="cMenu_copy"/>
       <menuitem id="cMenu_paste"/>
--- a/devtools/client/inspector/computed/test/browser.ini
+++ b/devtools/client/inspector/computed/test/browser.ini
@@ -27,11 +27,13 @@ support-files =
 [browser_computed_media-queries.js]
 [browser_computed_no-results-placeholder.js]
 [browser_computed_original-source-link.js]
 [browser_computed_pseudo-element_01.js]
 [browser_computed_refresh-on-style-change_01.js]
 [browser_computed_search-filter.js]
 [browser_computed_search-filter_clear.js]
 [browser_computed_search-filter_context-menu.js]
+subsuite = clipboard
 [browser_computed_search-filter_escape-keypress.js]
 [browser_computed_select-and-copy-styles.js]
+subsuite = clipboard
 [browser_computed_style-editor-link.js]
--- a/devtools/client/inspector/markup/test/browser.ini
+++ b/devtools/client/inspector/markup/test/browser.ini
@@ -56,16 +56,17 @@ skip-if = os == "mac" # Full keyboard na
 skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard Preferences
 [browser_markup_accessibility_semantics.js]
 [browser_markup_anonymous_01.js]
 [browser_markup_anonymous_02.js]
 skip-if = e10s # scratchpad.xul is not loading in e10s window
 [browser_markup_anonymous_03.js]
 [browser_markup_anonymous_04.js]
 [browser_markup_copy_image_data.js]
+subsuite = clipboard
 [browser_markup_css_completion_style_attribute_01.js]
 [browser_markup_css_completion_style_attribute_02.js]
 [browser_markup_dragdrop_autoscroll.js]
 [browser_markup_dragdrop_distance.js]
 [browser_markup_dragdrop_draggable.js]
 [browser_markup_dragdrop_dragRootNode.js]
 [browser_markup_dragdrop_escapeKeyPress.js]
 [browser_markup_dragdrop_invalidNodes.js]
@@ -85,16 +86,17 @@ skip-if = e10s # scratchpad.xul is not l
 [browser_markup_events_jquery_1.11.1.js]
 [browser_markup_events_jquery_2.1.1.js]
 [browser_markup_events-overflow.js]
 skip-if = true # Bug 1177550
 [browser_markup_links_01.js]
 [browser_markup_links_02.js]
 [browser_markup_links_03.js]
 [browser_markup_links_04.js]
+subsuite = clipboard
 [browser_markup_links_05.js]
 [browser_markup_links_06.js]
 [browser_markup_links_07.js]
 [browser_markup_load_01.js]
 [browser_markup_html_edit_01.js]
 [browser_markup_html_edit_02.js]
 [browser_markup_html_edit_03.js]
 [browser_markup_image_tooltip.js]
--- a/devtools/client/inspector/rules/test/browser.ini
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -81,16 +81,17 @@ support-files =
 [browser_rules_completion-popup-hidden-after-navigation.js]
 [browser_rules_content_01.js]
 [browser_rules_content_02.js]
 skip-if = e10s && debug # Bug 1250058 - Docshell leak on debug e10s
 [browser_rules_context-menu-show-mdn-docs-01.js]
 [browser_rules_context-menu-show-mdn-docs-02.js]
 [browser_rules_context-menu-show-mdn-docs-03.js]
 [browser_rules_copy_styles.js]
+subsuite = clipboard
 [browser_rules_cssom.js]
 [browser_rules_cubicbezier-appears-on-swatch-click.js]
 [browser_rules_cubicbezier-commit-on-ENTER.js]
 [browser_rules_cubicbezier-revert-on-ESC.js]
 [browser_rules_custom.js]
 [browser_rules_cycle-angle.js]
 [browser_rules_cycle-color.js]
 [browser_rules_edit-property-cancel.js]
@@ -178,18 +179,20 @@ skip-if = (os == "win" && debug) # bug 9
 [browser_rules_search-filter_04.js]
 [browser_rules_search-filter_05.js]
 [browser_rules_search-filter_06.js]
 [browser_rules_search-filter_07.js]
 [browser_rules_search-filter_08.js]
 [browser_rules_search-filter_09.js]
 [browser_rules_search-filter_10.js]
 [browser_rules_search-filter_context-menu.js]
+subsuite = clipboard
 [browser_rules_search-filter_escape-keypress.js]
 [browser_rules_select-and-copy-styles.js]
+subsuite = clipboard
 [browser_rules_selector-highlighter_01.js]
 [browser_rules_selector-highlighter_02.js]
 [browser_rules_selector-highlighter_03.js]
 [browser_rules_selector_highlight.js]
 [browser_rules_strict-search-filter-computed-list_01.js]
 [browser_rules_strict-search-filter_01.js]
 [browser_rules_strict-search-filter_02.js]
 [browser_rules_strict-search-filter_03.js]
--- a/devtools/client/inspector/shared/test/browser.ini
+++ b/devtools/client/inspector/shared/test/browser.ini
@@ -15,17 +15,19 @@ support-files =
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/framework/test/shared-head.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_styleinspector_context-menu-copy-color_01.js]
 [browser_styleinspector_context-menu-copy-color_02.js]
+subsuite = clipboard
 [browser_styleinspector_context-menu-copy-urls.js]
+subsuite = clipboard
 [browser_styleinspector_csslogic-content-stylesheets.js]
 skip-if = e10s && debug # Bug 1250058 (docshell leak when opening 2 toolboxes)
 [browser_styleinspector_output-parser.js]
 [browser_styleinspector_refresh_when_active.js]
 [browser_styleinspector_tooltip-background-image.js]
 [browser_styleinspector_tooltip-closes-on-new-selection.js]
 skip-if = e10s # Bug 1111546 (e10s)
 [browser_styleinspector_tooltip-longhand-fontfamily.js]
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -91,20 +91,24 @@ skip-if = os == "mac" # Full keyboard na
 [browser_inspector_highlighter-zoom.js]
 [browser_inspector_iframe-navigation.js]
 [browser_inspector_infobar_01.js]
 [browser_inspector_initialization.js]
 skip-if = (e10s && debug) # Bug 1250058 - Docshell leak on debug e10s
 [browser_inspector_inspect-object-element.js]
 [browser_inspector_invalidate.js]
 [browser_inspector_keyboard-shortcuts-copy-outerhtml.js]
+subsuite = clipboard
 [browser_inspector_keyboard-shortcuts.js]
 [browser_inspector_menu-01-sensitivity.js]
+subsuite = clipboard
 [browser_inspector_menu-02-copy-items.js]
+subsuite = clipboard
 [browser_inspector_menu-03-paste-items.js]
+subsuite = clipboard
 [browser_inspector_menu-04-use-in-console.js]
 [browser_inspector_menu-05-attribute-items.js]
 [browser_inspector_menu-06-other.js]
 [browser_inspector_navigation.js]
 [browser_inspector_pane-toggle-01.js]
 [browser_inspector_pane-toggle-02.js]
 [browser_inspector_pane-toggle-03.js]
 [browser_inspector_pane-toggle-04.js]
--- a/devtools/client/jsonview/test/browser.ini
+++ b/devtools/client/jsonview/test/browser.ini
@@ -12,14 +12,17 @@ support-files =
   simple_json.json^headers^
   valid_json.json
   valid_json.json^headers^
   !/devtools/client/commandline/test/head.js
   !/devtools/client/framework/test/head.js
   !/devtools/client/framework/test/shared-head.js
 
 [browser_jsonview_copy_headers.js]
+subsuite = clipboard
 [browser_jsonview_copy_json.js]
+subsuite = clipboard
 [browser_jsonview_copy_rawdata.js]
+subsuite = clipboard
 [browser_jsonview_filter.js]
 [browser_jsonview_invalid_json.js]
 [browser_jsonview_valid_json.js]
 [browser_jsonview_save_json.js]
--- a/devtools/client/locales/en-US/toolbox.dtd
+++ b/devtools/client/locales/en-US/toolbox.dtd
@@ -6,41 +6,17 @@
 <!-- LOCALIZATION NOTE : FILE Do not translate key -->
 
 <!ENTITY closeCmd.key  "W">
 <!ENTITY toggleToolbox.key  "I">
 <!ENTITY toggleToolboxF12.keycode          "VK_F12">
 <!ENTITY toggleToolboxF12.keytext          "F12">
 
 <!ENTITY toolboxCloseButton.tooltip    "Close Developer Tools">
-<!ENTITY toolboxOptionsButton.key      "O">
-<!ENTITY toolboxNextTool.key           "]">
-<!ENTITY toolboxPreviousTool.key       "[">
 
-<!-- LOCALIZATION NOTE :
-fullZoomEnlargeCmd.commandkey, fullZoomEnlargeCmd.commandkey2,
-fullZoomEnlargeCmd.commandkey3, fullZoomReduceCmd.commandkey,
-fullZoomReduceCmd.commandkey2, fullZoomResetCmd.commandkey,
-and fullZoomResetCmd.commandkey2 should all match the corresponding
-values from browser.dtd.  -->
-<!ENTITY fullZoomEnlargeCmd.commandkey  "+">
-<!ENTITY fullZoomEnlargeCmd.commandkey2 "=">
-<!ENTITY fullZoomEnlargeCmd.commandkey3 "">
-
-<!ENTITY fullZoomReduceCmd.commandkey   "-">
-<!ENTITY fullZoomReduceCmd.commandkey2  "">
-
-<!ENTITY fullZoomResetCmd.commandkey    "0">
-<!ENTITY fullZoomResetCmd.commandkey2   "">
-
-<!ENTITY toolboxReload.key             "r">
-<!-- This key is used with the accel+shift modifiers to minimize the toolbox -->
-<!ENTITY toolboxToggleMinimize.key     "U">
-
-<!ENTITY toolboxToggle.key             "d">
 <!-- LOCALIZATION NOTE (toolboxFramesButton): This is the label for
   -  the iframes menu list that appears only when the document has some.
   -  It allows you to switch the context of the whole toolbox. -->
 <!ENTITY toolboxFramesTooltip          "Select an iframe as the currently targeted document">
 
 <!-- LOCALIZATION NOTE (toolboxNoAutoHideButton): This is the label for
   -  the button to force the popups/panels to stay visible on blur.
   -  This is only visible in the browser toolbox as it is meant for
--- a/devtools/client/locales/en-US/toolbox.properties
+++ b/devtools/client/locales/en-US/toolbox.properties
@@ -130,8 +130,55 @@ toolbox.viewCssSourceInStyleEditor.label
 
 # LOCALIZATION NOTE (toolbox.viewJsSourceInDebugger.label)
 # Used as a message in either tooltips or contextual menu items to open the
 # corresponding URL as a js file in the Debugger tool.
 # DEV NOTE: Mostly used wherever toolbox.viewSourceInDebugger is used.
 toolbox.viewJsSourceInDebugger.label=Open File in Debugger
 
 toolbox.resumeOrderWarning=Page did not resume after the debugger was attached. To fix this, please close and re-open the toolbox.
+
+# LOCALIZATION NOTE (toolbox.options.key)
+# Key shortcut used to open the options panel
+toolbox.options.key=CmdOrCtrl+Shift+O
+
+# LOCALIZATION NOTE (toolbox.help.key)
+# Key shortcut used to open the options panel
+toolbox.help.key=F1
+
+# LOCALIZATION NOTE (toolbox.nextTool.key)
+# Key shortcut used to select the next tool
+toolbox.nextTool.key=CmdOrCtrl+]
+
+# LOCALIZATION NOTE (toolbox.previousTool.key)
+# Key shortcut used to select the previous tool
+toolbox.previousTool.key=CmdOrCtrl+[
+
+# LOCALIZATION NOTE (toolbox.zoom*.key)
+# Key shortcuts used to zomm in/out or reset the toolbox
+# Should match fullZoom*Cmd.commandkey values from browser.dtd
+toolbox.zoomIn.key=CmdOrCtrl+Plus
+toolbox.zoomIn2.key=CmdOrCtrl+=
+toolbox.zoomIn3.key=
+
+toolbox.zoomOut.key=CmdOrCtrl+-
+toolbox.zoomOut2.key=
+
+toolbox.zoomReset.key=CmdOrCtrl+0
+toolbox.zoomReset2.key=
+
+# LOCALIZATION NOTE (toolbox.reload*.key)
+# Key shortcuts used to reload the page
+toolbox.reload.key=CmdOrCtrl+R
+toolbox.reload2.key=F5
+
+# LOCALIZATION NOTE (toolbox.forceReload*.key)
+# Key shortcuts used to force reload of the page by bypassing caches
+toolbox.forceReload.key=CmdOrCtrl+Shift+R
+toolbox.forceReload2.key=CmdOrCtrl+F5
+
+# LOCALIZATION NOTE (toolbox.minimize.key)
+# Key shortcut used to minimize the toolbox
+toolbox.minimize.key=CmdOrCtrl+Shift+U
+
+# LOCALIZATION NOTE (toolbox.toggleHost.key)
+# Key shortcut used to move the toolbox in bottom or side of the browser window
+toolbox.toggleHost.key=CmdOrCtrl+Shift+D
--- a/devtools/client/netmonitor/har/test/browser.ini
+++ b/devtools/client/netmonitor/har/test/browser.ini
@@ -1,11 +1,11 @@
 [DEFAULT]
 tags = devtools
-subsuite = devtools
+subsuite = clipboard
 support-files =
   head.js
   html_har_post-data-test-page.html
   !/devtools/client/netmonitor/test/head.js
   !/devtools/client/netmonitor/test/html_simple-test-page.html
 
 [browser_net_har_copy_all_as_har.js]
 [browser_net_har_post_data.js]
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -54,22 +54,29 @@ skip-if = (toolkit == "cocoa" && e10s) #
 [browser_net_charts-05.js]
 [browser_net_charts-06.js]
 [browser_net_charts-07.js]
 [browser_net_clear.js]
 [browser_net_complex-params.js]
 [browser_net_content-type.js]
 [browser_net_curl-utils.js]
 [browser_net_copy_image_as_data_uri.js]
+subsuite = clipboard
 [browser_net_copy_svg_image_as_data_uri.js]
+subsuite = clipboard
 [browser_net_copy_url.js]
+subsuite = clipboard
 [browser_net_copy_params.js]
+subsuite = clipboard
 [browser_net_copy_response.js]
+subsuite = clipboard
 [browser_net_copy_headers.js]
+subsuite = clipboard
 [browser_net_copy_as_curl.js]
+subsuite = clipboard
 skip-if = e10s # Bug 1091596
 [browser_net_cyrillic-01.js]
 [browser_net_cyrillic-02.js]
 [browser_net_details-no-duplicated-content.js]
 skip-if = (os == 'linux' && e10s && debug) # Bug 1242204
 [browser_net_filter-01.js]
 [browser_net_filter-02.js]
 [browser_net_filter-03.js]
--- a/devtools/client/shared/key-shortcuts.js
+++ b/devtools/client/shared/key-shortcuts.js
@@ -29,17 +29,16 @@ const ElectronKeysMapping = {
   "F17": "DOM_VK_F17",
   "F18": "DOM_VK_F18",
   "F19": "DOM_VK_F19",
   "F20": "DOM_VK_F20",
   "F21": "DOM_VK_F21",
   "F22": "DOM_VK_F22",
   "F23": "DOM_VK_F23",
   "F24": "DOM_VK_F24",
-  "Plus": "DOM_VK_PLUS",
   "Space": "DOM_VK_SPACE",
   "Backspace": "DOM_VK_BACK_SPACE",
   "Delete": "DOM_VK_DELETE",
   "Insert": "DOM_VK_INSERT",
   "Return": "DOM_VK_RETURN",
   "Enter": "DOM_VK_RETURN",
   "Up": "DOM_VK_UP",
   "Down": "DOM_VK_DOWN",
@@ -117,30 +116,62 @@ KeyShortcuts.parseElectronKey = function
       shortcut.ctrl = true;
     } else if (mod === "Shift") {
       shortcut.shift = true;
     } else {
       throw new Error("Unsupported modifier: " + mod);
     }
   }
 
-  if (typeof (key) === "string" && key.length === 1) {
+  // Plus is a special case. It's a character key and shouldn't be matched
+  // against a keycode as it is only accessible via Shift/Capslock
+  if (key === "Plus") {
+    key = "+";
+  }
+
+  if (typeof key === "string" && key.length === 1) {
     // Match any single character
     shortcut.key = key.toLowerCase();
   } else if (key in ElectronKeysMapping) {
     // Maps the others manually to DOM API DOM_VK_*
     key = ElectronKeysMapping[key];
     shortcut.keyCode = window.KeyboardEvent[key];
+    // Used only to stringify the shortcut
+    shortcut.keyCodeString = key;
   } else {
     throw new Error("Unsupported key: " + key);
   }
 
   return shortcut;
 };
 
+KeyShortcuts.stringify = function (shortcut) {
+  let list = [];
+  if (shortcut.alt) {
+    list.push("Alt");
+  }
+  if (shortcut.ctrl) {
+    list.push("Ctrl");
+  }
+  if (shortcut.meta) {
+    list.push("Cmd");
+  }
+  if (shortcut.shift) {
+    list.push("Shift");
+  }
+  let key;
+  if (shortcut.key) {
+    key = shortcut.key.toUpperCase();
+  } else {
+    key = shortcut.keyCodeString;
+  }
+  list.push(key);
+  return list.join("+");
+};
+
 KeyShortcuts.prototype = {
   destroy() {
     this.window.removeEventListener("keydown", this);
     this.keys.clear();
   },
 
   doesEventMatchShortcut(event, shortcut) {
     if (shortcut.meta != event.metaKey) {
@@ -156,17 +187,22 @@ KeyShortcuts.prototype = {
     // expected key is a special character accessible via shift.
     if (shortcut.shift != event.shiftKey && event.key &&
         event.key.match(/[a-zA-Z]/)) {
       return false;
     }
     if (shortcut.keyCode) {
       return event.keyCode == shortcut.keyCode;
     }
-    return event.key.toLowerCase() == shortcut.key;
+    // For character keys, we match if the final character is the expected one.
+    // But for digits we also accept indirect match to please azerty keyboard,
+    // which requires Shift to be pressed to get digits.
+    return event.key.toLowerCase() == shortcut.key ||
+      (shortcut.key.match(/[0-9]/) &&
+       event.keyCode == shortcut.key.charCodeAt(0));
   },
 
   handleEvent(event) {
     for (let [key, shortcut] of this.keys) {
       if (this.doesEventMatchShortcut(event, shortcut)) {
         this.eventEmitter.emit(key, event);
       }
     }
--- a/devtools/client/shared/test/browser.ini
+++ b/devtools/client/shared/test/browser.ini
@@ -112,16 +112,18 @@ skip-if = e10s # Bug 1221911, bug 122228
 skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
 [browser_graphs-16.js]
 skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
 [browser_html_tooltip-01.js]
 [browser_html_tooltip-02.js]
 [browser_html_tooltip-03.js]
 [browser_html_tooltip-04.js]
 [browser_html_tooltip-05.js]
+[browser_html_tooltip_arrow-01.js]
+[browser_html_tooltip_arrow-02.js]
 [browser_inplace-editor-01.js]
 [browser_inplace-editor-02.js]
 [browser_inplace-editor_maxwidth.js]
 [browser_key_shortcuts.js]
 [browser_layoutHelpers.js]
 skip-if = e10s # Layouthelpers test should not run in a content page.
 [browser_layoutHelpers-getBoxQuads.js]
 skip-if = e10s # Layouthelpers test should not run in a content page.
--- a/devtools/client/shared/test/browser_html_tooltip-02.js
+++ b/devtools/client/shared/test/browser_html_tooltip-02.js
@@ -6,16 +6,17 @@
 
 /**
  * Test the HTMLTooltip is closed when clicking outside of its container.
  */
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
   <?xml-stylesheet href="chrome://global/skin/global.css"?>
+  <?xml-stylesheet href="chrome://devtools/skin/common.css"?>
   <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
    title="Tooltip test">
     <vbox flex="1">
       <hbox id="box1" flex="1">test1</hbox>
       <hbox id="box2" flex="1">test2</hbox>
       <hbox id="box3" flex="1">test3</hbox>
       <hbox id="box4" flex="1">test4</hbox>
     </vbox>
--- a/devtools/client/shared/test/browser_html_tooltip-03.js
+++ b/devtools/client/shared/test/browser_html_tooltip-03.js
@@ -6,16 +6,17 @@
 
 /**
  * Test the HTMLTooltip autofocus configuration option.
  */
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
   <?xml-stylesheet href="chrome://global/skin/global.css"?>
+  <?xml-stylesheet href="chrome://devtools/skin/common.css"?>
   <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
    title="Tooltip test">
     <vbox flex="1">
       <hbox id="box1" flex="1">test1</hbox>
       <hbox id="box2" flex="1">test2</hbox>
       <hbox id="box3" flex="1">test3</hbox>
       <hbox id="box4" flex="1">
         <textbox id="box4-input"></textbox>
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/browser_html_tooltip_arrow-01.js
@@ -0,0 +1,95 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+/* import-globals-from helper_html_tooltip.js */
+
+"use strict";
+
+/**
+ * Test the HTMLTooltip "arrow" type on small anchors. The arrow should remain
+ * aligned with the anchors as much as possible
+ */
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const getAnchor = function (position) {
+  return `<html:div class="anchor" style="width:10px;
+                                          height: 10px;
+                                          position: absolute;
+                                          background: red;
+                                          ${position}"></html:div>`;
+};
+
+const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
+  <?xml-stylesheet href="chrome://global/skin/global.css"?>
+  <?xml-stylesheet href="chrome://devtools/skin/common.css"?>
+  <?xml-stylesheet href="chrome://devtools/skin/light-theme.css"?>
+
+  <window class="theme-light"
+          xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+          xmlns:html="http://www.w3.org/1999/xhtml"
+          title="Tooltip test">
+    <vbox flex="1" style="position: relative">
+      ${getAnchor("top: 0; left: 0;")}
+      ${getAnchor("top: 0; left: 25px;")}
+      ${getAnchor("top: 0; left: 50px;")}
+      ${getAnchor("top: 0; left: 75px;")}
+      ${getAnchor("bottom: 0; left: 0;")}
+      ${getAnchor("bottom: 0; left: 25px;")}
+      ${getAnchor("bottom: 0; left: 50px;")}
+      ${getAnchor("bottom: 0; left: 75px;")}
+      ${getAnchor("bottom: 0; right: 0;")}
+      ${getAnchor("bottom: 0; right: 25px;")}
+      ${getAnchor("bottom: 0; right: 50px;")}
+      ${getAnchor("bottom: 0; right: 75px;")}
+      ${getAnchor("top: 0; right: 0;")}
+      ${getAnchor("top: 0; right: 25px;")}
+      ${getAnchor("top: 0; right: 50px;")}
+      ${getAnchor("top: 0; right: 75px;")}
+    </vbox>
+  </window>`;
+
+const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
+loadHelperScript("helper_html_tooltip.js");
+
+add_task(function* () {
+  // Force the toolbox to be 200px high;
+  yield pushPref("devtools.toolbox.footer.height", 200);
+
+  yield addTab("about:blank");
+  let [,, doc] = yield createHost("bottom", TEST_URI);
+
+  info("Create HTML tooltip");
+  let tooltip = new HTMLTooltip({doc}, {type: "arrow"});
+  let div = doc.createElementNS(HTML_NS, "div");
+  div.style.height = "100%";
+  yield tooltip.setContent(div, 200, 35);
+
+  let {right: docRight} = doc.documentElement.getBoundingClientRect();
+
+  let elements = [...doc.querySelectorAll(".anchor")];
+  for (let el of elements) {
+    info("Display the tooltip on an anchor.");
+    yield showTooltip(tooltip, el);
+
+    let arrow = tooltip.arrow;
+    ok(arrow, "Tooltip has an arrow");
+
+    // Get the geometry of the anchor, the tooltip frame & arrow.
+    let arrowBounds = arrow.getBoxQuads({relativeTo: doc})[0].bounds;
+    let frameBounds = tooltip.frame.getBoxQuads({relativeTo: doc})[0].bounds;
+    let anchorBounds = el.getBoxQuads({relativeTo: doc})[0].bounds;
+
+    let intersects = arrowBounds.left <= anchorBounds.right &&
+                     arrowBounds.right >= anchorBounds.left;
+    let isBlockedByViewport = arrowBounds.left == 0 ||
+                              arrowBounds.right == docRight;
+    ok(intersects || isBlockedByViewport,
+      "Tooltip arrow is aligned with the anchor, or stuck on viewport's edge.");
+
+    let isInFrame = arrowBounds.left >= frameBounds.left &&
+                    arrowBounds.right <= frameBounds.right;
+    ok(isInFrame,
+      "The tooltip arrow remains inside the tooltip frame horizontally");
+
+    yield hideTooltip(tooltip);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/browser_html_tooltip_arrow-02.js
@@ -0,0 +1,88 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+/* import-globals-from helper_html_tooltip.js */
+
+"use strict";
+
+/**
+ * Test the HTMLTooltip "arrow" type on wide anchors. The arrow should remain
+ * aligned with the anchors as much as possible
+ */
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const getAnchor = function (position) {
+  return `<html:div class="anchor" style="height: 5px;
+                                          position: absolute;
+                                          background: red;
+                                          ${position}"></html:div>`;
+};
+
+const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
+  <?xml-stylesheet href="chrome://global/skin/global.css"?>
+  <?xml-stylesheet href="chrome://devtools/skin/common.css"?>
+  <?xml-stylesheet href="chrome://devtools/skin/light-theme.css"?>
+
+  <window class="theme-light"
+          xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+          xmlns:html="http://www.w3.org/1999/xhtml"
+          title="Tooltip test">
+    <vbox flex="1" style="position: relative">
+      ${getAnchor("top:    0; left: 0; width: 50px;")}
+      ${getAnchor("top: 10px; left: 0; width: 100px;")}
+      ${getAnchor("top: 20px; left: 0; width: 150px;")}
+      ${getAnchor("top: 30px; left: 0; width: 200px;")}
+      ${getAnchor("top: 40px; left: 0; width: 250px;")}
+      ${getAnchor("top: 50px; left: 100px; width: 250px;")}
+      ${getAnchor("top: 100px; width:  50px; right: 0;")}
+      ${getAnchor("top: 110px; width: 100px; right: 0;")}
+      ${getAnchor("top: 120px; width: 150px; right: 0;")}
+      ${getAnchor("top: 130px; width: 200px; right: 0;")}
+      ${getAnchor("top: 140px; width: 250px; right: 0;")}
+    </vbox>
+  </window>`;
+
+const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
+loadHelperScript("helper_html_tooltip.js");
+
+add_task(function* () {
+  // Force the toolbox to be 200px high;
+  yield pushPref("devtools.toolbox.footer.height", 200);
+
+  yield addTab("about:blank");
+  let [,, doc] = yield createHost("bottom", TEST_URI);
+
+  info("Create HTML tooltip");
+  let tooltip = new HTMLTooltip({doc}, {type: "arrow"});
+  let div = doc.createElementNS(HTML_NS, "div");
+  div.style.height = "100%";
+  yield tooltip.setContent(div, 200, 35);
+
+  let {right: docRight} = doc.documentElement.getBoundingClientRect();
+
+  let elements = [...doc.querySelectorAll(".anchor")];
+  for (let el of elements) {
+    info("Display the tooltip on an anchor.");
+    yield showTooltip(tooltip, el);
+
+    let arrow = tooltip.arrow;
+    ok(arrow, "Tooltip has an arrow");
+
+    // Get the geometry of the anchor, the tooltip frame & arrow.
+    let arrowBounds = arrow.getBoxQuads({relativeTo: doc})[0].bounds;
+    let frameBounds = tooltip.frame.getBoxQuads({relativeTo: doc})[0].bounds;
+    let anchorBounds = el.getBoxQuads({relativeTo: doc})[0].bounds;
+
+    let intersects = arrowBounds.left <= anchorBounds.right &&
+                     arrowBounds.right >= anchorBounds.left;
+    let isBlockedByViewport = arrowBounds.left == 0 ||
+                              arrowBounds.right == docRight;
+    ok(intersects || isBlockedByViewport,
+      "Tooltip arrow is aligned with the anchor, or stuck on viewport's edge.");
+
+    let isInFrame = arrowBounds.left >= frameBounds.left &&
+                    arrowBounds.right <= frameBounds.right;
+    ok(isInFrame,
+      "The tooltip arrow remains inside the tooltip frame horizontally");
+    yield hideTooltip(tooltip);
+  }
+});
--- a/devtools/client/shared/test/browser_key_shortcuts.js
+++ b/devtools/client/shared/test/browser_key_shortcuts.js
@@ -4,17 +4,19 @@
 var isOSX = Services.appinfo.OS === "Darwin";
 
 add_task(function* () {
   let shortcuts = new KeyShortcuts({
     window
   });
   yield testSimple(shortcuts);
   yield testNonLetterCharacter(shortcuts);
+  yield testPlusCharacter(shortcuts);
   yield testMixup(shortcuts);
+  yield testLooseDigits(shortcuts);
   yield testExactModifiers(shortcuts);
   yield testLooseShiftModifier(shortcuts);
   yield testStrictLetterShiftModifier(shortcuts);
   yield testAltModifier(shortcuts);
   yield testCommandOrControlModifier(shortcuts);
   yield testCtrlModifier(shortcuts);
   shortcuts.destroy();
 });
@@ -56,16 +58,30 @@ function testNonLetterCharacter(shortcut
   let onKey = once(shortcuts, "[", (key, event) => {
     is(event.key, "[");
   });
 
   EventUtils.synthesizeKey("[", {}, window);
   yield onKey;
 }
 
+// Plus is special. It's keycode is the one for "=". That's because it requires
+// shift to be pressed and is behind "=" key. So it should be considered as a
+// character key
+function testPlusCharacter(shortcuts) {
+  info("Test 'Plus' key shortcuts");
+
+  let onKey = once(shortcuts, "Plus", (key, event) => {
+    is(event.key, "+");
+  });
+
+  EventUtils.synthesizeKey("+", { keyCode: 61, shiftKey: true }, window);
+  yield onKey;
+}
+
 // Test they listeners are not mixed up between shortcuts
 function testMixup(shortcuts) {
   info("Test possible listener mixup");
 
   let hitFirst = false, hitSecond = false;
   let onFirstKey = once(shortcuts, "0", (key, event) => {
     is(event.key, "0");
     hitFirst = true;
@@ -90,16 +106,50 @@ function testMixup(shortcuts) {
   ok(!hitSecond, "No mixup, second shortcut is still not notified (2/2)");
 
   // Finally dispatch the second shortcut
   EventUtils.synthesizeKey("a", { altKey: true }, window);
   yield onSecondKey;
   ok(hitSecond, "Got the second shortcut notified once it is actually fired");
 }
 
+// On azerty keyboard, digits are only available by pressing Shift/Capslock,
+// but we accept them even if we omit doing that.
+function testLooseDigits(shortcuts) {
+  info("Test Loose digits");
+  let onKey = once(shortcuts, "0", (key, event) => {
+    is(event.key, "à");
+    ok(!event.altKey);
+    ok(!event.ctrlKey);
+    ok(!event.metaKey);
+    ok(!event.shiftKey);
+  });
+  // Simulate a press on the "0" key, without shift pressed on a french
+  // keyboard
+  EventUtils.synthesizeKey(
+    "à",
+    { keyCode: 48 },
+    window);
+  yield onKey;
+
+  onKey = once(shortcuts, "0", (key, event) => {
+    is(event.key, "0");
+    ok(!event.altKey);
+    ok(!event.ctrlKey);
+    ok(!event.metaKey);
+    ok(event.shiftKey);
+  });
+  // Simulate the same press with shift pressed
+  EventUtils.synthesizeKey(
+    "0",
+    { keyCode: 48, shiftKey: true },
+    window);
+  yield onKey;
+}
+
 // Test that shortcuts is notified only when the modifiers match exactly
 function testExactModifiers(shortcuts) {
   info("Test exact modifiers match");
 
   let hit = false;
   let onKey = once(shortcuts, "Alt+A", (key, event) => {
     is(event.key, "a");
     ok(event.altKey);
--- a/devtools/client/shared/test/helper_html_tooltip.js
+++ b/devtools/client/shared/test/helper_html_tooltip.js
@@ -47,20 +47,20 @@ function* hideTooltip(tooltip) {
 /**
  * Forces the reflow of an HTMLTooltip document and waits for the next repaint.
  *
  * @param {HTMLTooltip} the tooltip to reflow
  * @return {Promise} a promise that will resolve after the reflow and repaint
  *         have been executed.
  */
 function waitForReflow(tooltip) {
-  let {document} = tooltip;
+  let {doc} = tooltip;
   return new Promise(resolve => {
-    document.documentElement.offsetWidth;
-    document.defaultView.requestAnimationFrame(resolve);
+    doc.documentElement.offsetWidth;
+    doc.defaultView.requestAnimationFrame(resolve);
   });
 }
 
 /**
  * Test helper designed to check that a tooltip is displayed at the expected
  * position relative to an anchor, given a set of expectations.
  *
  * @param {HTMLTooltip} tooltip
--- a/devtools/client/shared/widgets/HTMLTooltip.js
+++ b/devtools/client/shared/widgets/HTMLTooltip.js
@@ -8,101 +8,153 @@
 
 const EventEmitter = require("devtools/shared/event-emitter");
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 
 const IFRAME_URL = "chrome://devtools/content/shared/widgets/tooltip-frame.xhtml";
 const IFRAME_CONTAINER_ID = "tooltip-iframe-container";
 
+const POSITION = {
+  TOP: "top",
+  BOTTOM: "bottom",
+};
+
+module.exports.POSITION = POSITION;
+
+const TYPE = {
+  NORMAL: "normal",
+  ARROW: "arrow",
+};
+
+module.exports.TYPE = TYPE;
+
+const ARROW_WIDTH = 32;
+
+// Default offset between the tooltip's left edge and the tooltip arrow.
+const ARROW_OFFSET = 20;
+
+const EXTRA_HEIGHT = {
+  "normal": 0,
+  // The arrow is 16px tall, but merges on 3px with the panel border
+  "arrow": 13,
+};
+
+const EXTRA_BORDER = {
+  "normal": 0,
+  "arrow": 3,
+};
+
 /**
  * The HTMLTooltip can display HTML content in a tooltip popup.
  *
  * @param {Toolbox} toolbox
  *        The devtools toolbox, needed to get the devtools main window.
  * @param {Object}
  *        - {String} type
- *          Display type of the tooltip. Possible values: "normal"
+ *          Display type of the tooltip. Possible values: "normal", "arrow"
  *        - {Boolean} autofocus
  *          Defaults to true. Should the tooltip be focused when opening it.
  *        - {Boolean} consumeOutsideClicks
  *          Defaults to true. The tooltip is closed when clicking outside.
  *          Should this event be stopped and consumed or not.
  */
 function HTMLTooltip(toolbox,
   {type = "normal", autofocus = true, consumeOutsideClicks = true} = {}) {
   EventEmitter.decorate(this);
 
-  this.document = toolbox.doc;
+  this.doc = toolbox.doc;
   this.type = type;
   this.autofocus = autofocus;
   this.consumeOutsideClicks = consumeOutsideClicks;
 
   // Use the topmost window to listen for click events to close the tooltip
-  this.topWindow = this.document.defaultView.top;
+  this.topWindow = this.doc.defaultView.top;
 
   this._onClick = this._onClick.bind(this);
 
   this.container = this._createContainer();
 
   // Promise that will resolve when the container can be filled with content.
   this.containerReady = new Promise(resolve => {
     if (this._isXUL()) {
       // In XUL context, load a placeholder document in the iframe container.
       let onLoad = () => {
-        this.container.removeEventListener("load", onLoad, true);
+        this.frame.removeEventListener("load", onLoad, true);
         resolve();
       };
 
-      this.container.addEventListener("load", onLoad, true);
-      this.container.setAttribute("src", IFRAME_URL);
+      this.frame.addEventListener("load", onLoad, true);
+      this.frame.setAttribute("src", IFRAME_URL);
+      this.doc.querySelector("window").appendChild(this.container);
     } else {
       // In non-XUL context the container is ready to use as is.
+      this.doc.body.appendChild(this.container);
       resolve();
     }
   });
 }
 
 module.exports.HTMLTooltip = HTMLTooltip;
 
 HTMLTooltip.prototype = {
-  position: {
-    TOP: "top",
-    BOTTOM: "bottom",
+  /**
+   * The tooltip frame is the child of the tooltip container that will only
+   * contain the tooltip content (and not the arrow or any other tooltip styling
+   * element).
+   * In XUL contexts, this is an iframe. In non XUL contexts this is a div,
+   * which also happens to be the tooltip.panel property.
+   */
+  get frame() {
+    return this.container.querySelector(".tooltip-panel");
   },
 
-  get parent() {
-    if (this._isXUL()) {
-      // In XUL context, we are wrapping the HTML content in an iframe.
-      let win = this.container.contentWindow.wrappedJSObject;
-      return win.document.getElementById(IFRAME_CONTAINER_ID);
+  /**
+   * The tooltip panel is the parentNode of the tooltip content provided in
+   * setContent().
+   */
+  get panel() {
+    if (!this._isXUL()) {
+      return this.frame;
     }
-    return this.container;
+    // In XUL context, the content is wrapped in an iframe.
+    let win = this.frame.contentWindow.wrappedJSObject;
+    return win.document.getElementById(IFRAME_CONTAINER_ID);
+  },
+
+  /**
+   * The arrow element. Might be null depending on the tooltip type.
+   */
+  get arrow() {
+    return this.container.querySelector(".tooltip-arrow");
   },
 
   /**
    * Set the tooltip content element. The preferred width/height should also be
    * specified here.
    *
    * @param {Element} content
    *        The tooltip content, should be a HTML element.
    * @param {Number} width
    *        Preferred width for the tooltip container
    * @param {Number} height
    *        Preferred height for the tooltip container
    * @return {Promise} a promise that will resolve when the content has been
    *         added in the tooltip container.
    */
   setContent: function (content, width, height) {
-    this.preferredWidth = width;
-    this.preferredHeight = height;
+    let themeHeight = EXTRA_HEIGHT[this.type] + 2 * EXTRA_BORDER[this.type];
+    let themeWidth = 2 * EXTRA_BORDER[this.type];
+
+    this.preferredWidth = width + themeWidth;
+    this.preferredHeight = height + themeHeight;
 
     return this.containerReady.then(() => {
-      this.parent.innerHTML = "";
-      this.parent.appendChild(content);
+      this.panel.innerHTML = "";
+      this.panel.appendChild(content);
     });
   },
 
   /**
    * Show the tooltip next to the provided anchor element. A preferred position
    * can be set. The event "shown" will be fired after the tooltip is displayed.
    *
    * @param {Element} anchor
@@ -110,186 +162,217 @@ HTMLTooltip.prototype = {
    * @param {Object}
    *        - {String} position: optional, possible values: top|bottom
    *          If layout permits, the tooltip will be displayed on top/bottom
    *          of the anchor. If ommitted, the tooltip will be displayed where
    *          more space is available.
    */
   show: function (anchor, {position} = {}) {
     this.containerReady.then(() => {
-      let {top, left, width, height} = this._findBestPosition(anchor, position);
+      let computedPosition = this._findBestPosition(anchor, position);
+
+      let isTop = computedPosition.position === POSITION.TOP;
+      this.container.classList.toggle("tooltip-top", isTop);
+      this.container.classList.toggle("tooltip-bottom", !isTop);
 
-      if (this._isXUL()) {
-        this.container.setAttribute("width", width);
-        this.container.setAttribute("height", height);
-      } else {
-        this.container.style.width = width + "px";
-        this.container.style.height = height + "px";
+      this.container.style.width = computedPosition.width + "px";
+      this.container.style.height = computedPosition.height + "px";
+      this.container.style.top = computedPosition.top + "px";
+      this.container.style.left = computedPosition.left + "px";
+
+      if (this.type === TYPE.ARROW) {
+        this.arrow.style.left = computedPosition.arrowLeft + "px";
       }
 
-      this.container.style.top = top + "px";
-      this.container.style.left = left + "px";
-      this.container.style.display = "block";
+      this.container.classList.add("tooltip-visible");
 
-      if (this.autofocus) {
-        this.container.focus();
-      }
-
-      this.attachEventsTimer = this.document.defaultView.setTimeout(() => {
+      this.attachEventsTimer = this.doc.defaultView.setTimeout(() => {
+        if (this.autofocus) {
+          this.frame.focus();
+        }
         this.topWindow.addEventListener("click", this._onClick, true);
         this.emit("shown");
       }, 0);
     });
   },
 
   /**
    * Hide the current tooltip. The event "hidden" will be fired when the tooltip
    * is hidden.
    */
   hide: function () {
-    this.document.defaultView.clearTimeout(this.attachEventsTimer);
+    this.doc.defaultView.clearTimeout(this.attachEventsTimer);
 
     if (this.isVisible()) {
       this.topWindow.removeEventListener("click", this._onClick, true);
-      this.container.style.display = "none";
+      this.container.classList.remove("tooltip-visible");
       this.emit("hidden");
     }
   },
 
   /**
    * Check if the tooltip is currently displayed.
    * @return {Boolean} true if the tooltip is visible
    */
   isVisible: function () {
-    let win = this.document.defaultView;
-    return win.getComputedStyle(this.container).display != "none";
+    return this.container.classList.contains("tooltip-visible");
   },
 
   /**
    * Destroy the tooltip instance. Hide the tooltip if displayed, remove the
    * tooltip container from the document.
    */
   destroy: function () {
     this.hide();
     this.container.remove();
   },
 
   _createContainer: function () {
-    let container;
+    let container = this.doc.createElementNS(XHTML_NS, "div");
+    container.setAttribute("type", this.type);
+    container.classList.add("tooltip-container");
+
+    let html;
     if (this._isXUL()) {
-      container = this.document.createElementNS(XHTML_NS, "iframe");
-      container.classList.add("devtools-tooltip-iframe");
-      this.document.querySelector("window").appendChild(container);
+      html = '<iframe class="devtools-tooltip-iframe tooltip-panel"></iframe>';
     } else {
-      container = this.document.createElementNS(XHTML_NS, "div");
-      this.document.body.appendChild(container);
+      html = '<div class="tooltip-panel theme-body"></div>';
     }
 
-    container.classList.add("theme-body");
-    container.classList.add("devtools-htmltooltip-container");
-
+    if (this.type === TYPE.ARROW) {
+      html += '<div class="tooltip-arrow"></div>';
+    }
+    container.innerHTML = html;
     return container;
   },
 
   _onClick: function (e) {
     if (this._isInTooltipContainer(e.target)) {
       return;
     }
 
     this.hide();
     if (this.consumeOutsideClicks) {
       e.preventDefault();
       e.stopPropagation();
     }
   },
 
   _isInTooltipContainer: function (node) {
-    let contentWindow = this.parent.ownerDocument.defaultView;
+    let contentWindow = this.panel.ownerDocument.defaultView;
     let win = node.ownerDocument.defaultView;
 
     if (win === contentWindow) {
       // If node is in the same window as the tooltip, check if the tooltip
       // parent contains node.
-      return this.parent.contains(node);
+      return this.panel.contains(node);
     }
 
     // Otherwise check if the node window is in the tooltip window.
     while (win.parent && win.parent != win) {
       win = win.parent;
       if (win === contentWindow) {
         return true;
       }
     }
     return false;
   },
 
+  /**
+   * Calculates the best possible position to display the tooltip near the
+   * provided anchor. An optional position can be provided, but will be
+   * respected only if it doesn't force the tooltip to be resized.
+   *
+   * If the tooltip has to be resized, the position will be wherever the most
+   * space is available.
+   *
+   */
   _findBestPosition: function (anchor, position) {
-    let top, left;
-    let {TOP, BOTTOM} = this.position;
+    let {TOP, BOTTOM} = POSITION;
 
-    let {left: anchorLeft, top: anchorTop, height: anchorHeight}
-      = this._getRelativeRect(anchor, this.document);
-
-    let {bottom: docBottom, right: docRight} =
-      this.document.documentElement.getBoundingClientRect();
+    // Get anchor geometry
+    let {
+      left: anchorLeft, top: anchorTop,
+      height: anchorHeight, width: anchorWidth
+    } = this._getRelativeRect(anchor, this.doc);
 
-    let height = this.preferredHeight;
-    // Check if the popup can fit above the anchor.
+    // Get document geometry
+    let {bottom: docBottom, right: docRight} =
+      this.doc.documentElement.getBoundingClientRect();
+
+    // Calculate available space for the tooltip.
     let availableTop = anchorTop;
-    let fitsAbove = availableTop >= height;
-    // Check if the popup can fit below the anchor.
-    let availableBelow = docBottom - (anchorTop + anchorHeight);
-    let fitsBelow = availableBelow >= height;
+    let availableBottom = docBottom - (anchorTop + anchorHeight);
 
-    let isPositionSuitable = (fitsAbove && position === TOP)
-      || (fitsBelow && position === BOTTOM);
-    if (!isPositionSuitable) {
-      // If the preferred position does not fit the preferred height,
-      // pick the position offering the most height.
-      position = availableTop > availableBelow ? TOP : BOTTOM;
+    // Find POSITION
+    let keepPosition = false;
+    if (position === TOP) {
+      keepPosition = availableTop >= this.preferredHeight;
+    } else if (position === BOTTOM) {
+      keepPosition = availableBottom >= this.preferredHeight;
+    }
+    if (!keepPosition) {
+      position = availableTop > availableBottom ? TOP : BOTTOM;
     }
 
-    // Calculate height, capped by the maximum height available.
-    height = Math.min(height, Math.max(availableTop, availableBelow));
-    top = position === TOP ? anchorTop - height : anchorTop + anchorHeight;
+    // Calculate HEIGHT.
+    let availableHeight = position === TOP ? availableTop : availableBottom;
+    let height = Math.min(this.preferredHeight, availableHeight);
+    height = Math.floor(height);
 
+    // Calculate TOP.
+    let top = position === TOP ? anchorTop - height : anchorTop + anchorHeight;
+
+    // Calculate WIDTH.
     let availableWidth = docRight;
     let width = Math.min(this.preferredWidth, availableWidth);
 
-    // By default, align the tooltip's left edge with the anchor left edge.
-    if (anchorLeft + width <= docRight) {
-      left = anchorLeft;
-    } else {
-      // If the tooltip cannot fit, shift to the left just enough to fit.
-      left = docRight - width;
+    // Calculate LEFT.
+    // By default the tooltip is aligned with the anchor left edge. Unless this
+    // makes it overflow the viewport, in which case is shifts to the left.
+    let left = Math.min(anchorLeft, docRight - width);
+
+    // Calculate ARROW LEFT (tooltip's LEFT might be updated)
+    let arrowLeft;
+    // Arrow style tooltips may need to be shifted to the left
+    if (this.type === TYPE.ARROW) {
+      let arrowCenter = left + ARROW_OFFSET + ARROW_WIDTH / 2;
+      let anchorCenter = anchorLeft + anchorWidth / 2;
+      // If the anchor is too narrow, align the arrow and the anchor center.
+      if (arrowCenter > anchorCenter) {
+        left = Math.max(0, left - (arrowCenter - anchorCenter));
+      }
+      // Arrow's feft offset relative to the anchor.
+      arrowLeft = Math.min(ARROW_OFFSET, (anchorWidth - ARROW_WIDTH) / 2) | 0;
+      // Translate the coordinate to tooltip container
+      arrowLeft += anchorLeft - left;
+      // Make sure the arrow remains in the tooltip container.
+      arrowLeft = Math.min(arrowLeft, width - ARROW_WIDTH);
+      arrowLeft = Math.max(arrowLeft, 0);
     }
 
-    return {top, left, width, height};
+    return {top, left, width, height, position, arrowLeft};
   },
 
   /**
    * Get the bounding client rectangle for a given node, relative to a custom
    * reference element (instead of the default for getBoundingClientRect which
    * is always the element's ownerDocument).
    */
   _getRelativeRect: function (node, relativeTo) {
     // Width and Height can be taken from the rect.
     let {width, height} = node.getBoundingClientRect();
 
-    // Find the smallest top/left coordinates from all quads.
-    let top = Infinity, left = Infinity;
-    let quads = node.getBoxQuads({relativeTo: relativeTo});
-    for (let quad of quads) {
-      top = Math.min(top, quad.bounds.top);
-      left = Math.min(left, quad.bounds.left);
-    }
+    let quads = node.getBoxQuads({relativeTo});
+    let top = quads[0].bounds.top;
+    let left = quads[0].bounds.left;
 
     // Compute right and bottom coordinates using the rest of the data.
     let right = left + width;
     let bottom = top + height;
 
     return {top, right, bottom, left, width, height};
   },
 
   _isXUL: function () {
-    return this.document.documentElement.namespaceURI === XUL_NS;
+    return this.doc.documentElement.namespaceURI === XUL_NS;
   },
 };
--- a/devtools/client/shared/widgets/tooltip-frame.xhtml
+++ b/devtools/client/shared/widgets/tooltip-frame.xhtml
@@ -10,15 +10,16 @@
   <script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"/>
   <style>
     html, body, #tooltip-iframe-container {
       margin: 0;
       padding: 0;
       width: 100%;
       height: 100%;
       overflow: hidden;
+      color: var(--theme-body-color);
     }
   </style>
 </head>
 <body role="application" class="theme-body">
   <div id="tooltip-iframe-container"></div>
 </body>
 </html>
--- a/devtools/client/themes/common.css
+++ b/devtools/client/themes/common.css
@@ -241,20 +241,98 @@
   background-position: 0 0, 10px 10px;
 }
 
 .devtools-tooltip-iframe {
   border: none;
   background: transparent;
 }
 
-.devtools-htmltooltip-container {
+.tooltip-container {
   display: none;
   position: fixed;
   z-index: 9999;
+  display: none;
+  background: transparent;
+}
+
+.tooltip-panel{
+  background-color: var(--theme-tooltip-background);
+}
+
+.tooltip-visible {
+  display: block;
+}
+
+.tooltip-container[type="normal"] > .tooltip-panel {
+  height: 100%;
+  width: 100%;
+}
+
+/* Tooltip : arrow style */
+
+.tooltip-container[type="arrow"] {
+  filter: drop-shadow(0 3px 4px var(--theme-tooltip-shadow));
+}
+
+.tooltip-container[type="arrow"] > .tooltip-panel {
+  position: absolute;
+  box-sizing: border-box;
+  height: calc(100% - 13px);
+  width: 100%;
+
+  border: 3px solid var(--theme-tooltip-border);
+  border-radius: 5px;
+}
+
+.tooltip-top[type="arrow"] .tooltip-panel {
+  top: 0;
+}
+
+.tooltip-bottom[type="arrow"] .tooltip-panel {
+  bottom: 0;
+}
+
+.tooltip-arrow {
+  position: absolute;
+  height: 16px;
+  width: 32px;
+  overflow: hidden;
+}
+
+.tooltip-top .tooltip-arrow {
+  bottom: 0;
+}
+
+.tooltip-bottom .tooltip-arrow {
+  top: 0;
+}
+
+.tooltip-arrow:before {
+  content: "";
+  position: absolute;
+  width: 21px;
+  height: 21px;
+  margin-left: 4px;
+  background: linear-gradient(-45deg,
+    var(--theme-tooltip-background) 50%, transparent 50%);
+  border-color: var(--theme-tooltip-border);
+  border-style: solid;
+  border-width: 0px 3px 3px 0px;
+  border-radius: 3px;
+}
+
+.tooltip-bottom .tooltip-arrow:before {
+  margin-top: 4px;
+  transform: rotate(225deg);
+}
+
+.tooltip-top .tooltip-arrow:before {
+  margin-top: -12px;
+  transform: rotate(45deg);
 }
 
 /* links to source code, like displaying `myfile.js:45` */
 
 .devtools-source-link {
   font-family: var(--monospace-font-family);
   color: var(--theme-highlight-blue);
   cursor: pointer;
--- a/devtools/client/themes/variables.css
+++ b/devtools/client/themes/variables.css
@@ -58,16 +58,21 @@
   --theme-graphs-red: #e57180;
   --theme-graphs-grey: #cccccc;
   --theme-graphs-full-red: #f00;
   --theme-graphs-full-blue: #00f;
 
   /* Images */
   --theme-pane-collapse-image: url(chrome://devtools/skin/images/pane-collapse.svg);
   --theme-pane-expand-image: url(chrome://devtools/skin/images/pane-expand.svg);
+
+  /* Tooltips */
+  --theme-tooltip-border: #d9e1e8;
+  --theme-tooltip-background: rgba(255, 255, 255, .9);
+  --theme-tooltip-shadow: rgba(155, 155, 155, 0.26);
 }
 
 :root.theme-dark {
   --theme-body-background: #393f4c;
   --theme-sidebar-background: #393f4c;
   --theme-contrast-background: #ffb35b;
 
   --theme-tab-toolbar-background: #272b35;
@@ -109,16 +114,21 @@
   --theme-graphs-red: #eb5368;
   --theme-graphs-grey: #757873;
   --theme-graphs-full-red: #f00;
   --theme-graphs-full-blue: #00f;
 
   /* Images */
   --theme-pane-collapse-image: url(chrome://devtools/skin/images/pane-collapse.svg);
   --theme-pane-expand-image: url(chrome://devtools/skin/images/pane-expand.svg);
+
+  /* Tooltips */
+  --theme-tooltip-border: #434850;
+  --theme-tooltip-background: rgba(19, 28, 38, .9);
+  --theme-tooltip-shadow: rgba(25, 25, 25, 0.76);
 }
 
 :root.theme-firebug {
   --theme-body-background: #fcfcfc;
   --theme-sidebar-background: #fcfcfc;
   --theme-contrast-background: #e6b064;
 
   --theme-tab-toolbar-background: #ebeced;
--- a/devtools/client/webconsole/test/browser.ini
+++ b/devtools/client/webconsole/test/browser.ini
@@ -144,33 +144,36 @@ support-files =
   !/devtools/client/framework/test/shared-head.js
   !/devtools/client/netmonitor/test/sjs_cors-test-server.sjs
   !/image/test/mochitest/blue.png
 
 [browser_bug1045902_console_csp_ignore_reflected_xss_message.js]
 skip-if = (e10s && debug) || (e10s && os == 'win') # Bug 1221499 enabled these on windows
 [browser_bug664688_sandbox_update_after_navigation.js]
 [browser_bug_638949_copy_link_location.js]
+subsuite = clipboard
 [browser_bug_862916_console_dir_and_filter_off.js]
 skip-if = (e10s && (os == 'win' || os == 'mac')) # Bug 1243976
 [browser_bug_865288_repeat_different_objects.js]
 [browser_bug_865871_variables_view_close_on_esc_key.js]
 [browser_bug_869003_inspect_cross_domain_object.js]
 [browser_bug_871156_ctrlw_close_tab.js]
 [browser_cached_messages.js]
 [browser_console.js]
 [browser_console_addonsdk_loader_exception.js]
 [browser_console_clear_method.js]
 [browser_console_clear_on_reload.js]
 [browser_console_click_focus.js]
 [browser_console_consolejsm_output.js]
 [browser_console_copy_command.js]
+subsuite = clipboard
 [browser_console_dead_objects.js]
 skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
 [browser_console_copy_entire_message_context_menu.js]
+subsuite = clipboard
 [browser_console_error_source_click.js]
 [browser_console_filters.js]
 [browser_console_iframe_messages.js]
 [browser_console_keyboard_accessibility.js]
 [browser_console_log_inspectable_object.js]
 [browser_console_native_getters.js]
 [browser_console_navigation_marker.js]
 [browser_console_netlogging.js]
@@ -210,16 +213,17 @@ tags = mcb
 [browser_webconsole_bug_582201_duplicate_errors.js]
 [browser_webconsole_bug_583816_No_input_and_Tab_key_pressed.js]
 [browser_webconsole_bug_585237_line_limit.js]
 [browser_webconsole_bug_585956_console_trace.js]
 [browser_webconsole_bug_585991_autocomplete_keys.js]
 [browser_webconsole_bug_585991_autocomplete_popup.js]
 [browser_webconsole_bug_586388_select_all.js]
 [browser_webconsole_bug_587617_output_copy.js]
+subsuite = clipboard
 [browser_webconsole_bug_588342_document_focus.js]
 [browser_webconsole_bug_588730_text_node_insertion.js]
 [browser_webconsole_bug_588967_input_expansion.js]
 [browser_webconsole_bug_589162_css_filter.js]
 [browser_webconsole_bug_592442_closing_brackets.js]
 [browser_webconsole_bug_593003_iframe_wrong_hud.js]
 [browser_webconsole_bug_594497_history_arrow_keys.js]
 [browser_webconsole_bug_595223_file_uri.js]
@@ -236,31 +240,33 @@ skip-if = e10s # Bug 1042253 - webconsol
 [browser_webconsole_bug_600183_charset.js]
 [browser_webconsole_bug_601177_log_levels.js]
 [browser_webconsole_bug_601352_scroll.js]
 [browser_webconsole_bug_601667_filter_buttons.js]
 [browser_webconsole_bug_603750_websocket.js]
 [browser_webconsole_bug_611795.js]
 [browser_webconsole_bug_613013_console_api_iframe.js]
 [browser_webconsole_bug_613280_jsterm_copy.js]
+subsuite = clipboard
 [browser_webconsole_bug_613642_maintain_scroll.js]
 [browser_webconsole_bug_613642_prune_scroll.js]
 [browser_webconsole_bug_614793_jsterm_scroll.js]
 [browser_webconsole_bug_618078_network_exceptions.js]
 [browser_webconsole_bug_621644_jsterm_dollar.js]
 [browser_webconsole_bug_622303_persistent_filters.js]
 [browser_webconsole_bug_623749_ctrl_a_select_all_winnt.js]
 skip-if = os != "win"
 [browser_webconsole_bug_630733_response_redirect_headers.js]
 [browser_webconsole_bug_632275_getters_document_width.js]
 [browser_webconsole_bug_632347_iterators_generators.js]
 skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
 [browser_webconsole_bug_632817.js]
 [browser_webconsole_bug_642108_pruneTest.js]
 [browser_webconsole_autocomplete_and_selfxss.js]
+subsuite = clipboard
 [browser_webconsole_bug_644419_log_limits.js]
 [browser_webconsole_bug_646025_console_file_location.js]
 [browser_webconsole_bug_651501_document_body_autocomplete.js]
 [browser_webconsole_bug_653531_highlighter_console_helper.js]
 skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
 [browser_webconsole_bug_658368_time_methods.js]
 [browser_webconsole_bug_659907_console_dir.js]
 [browser_webconsole_bug_660806_history_nav.js]
@@ -323,16 +329,17 @@ skip-if = e10s # Bug 1042253 - webconsol
 [browser_webconsole_netlogging.js]
 [browser_webconsole_netlogging_basic.js]
 [browser_webconsole_netlogging_panel.js]
 [browser_webconsole_netlogging_reset_filter.js]
 [browser_webconsole_notifications.js]
 [browser_webconsole_open-links-without-callback.js]
 [browser_webconsole_promise.js]
 [browser_webconsole_output_copy_newlines.js]
+subsuite = clipboard
 [browser_webconsole_output_order.js]
 [browser_webconsole_property_provider.js]
 skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
 [browser_webconsole_scratchpad_panel_link.js]
 [browser_webconsole_split.js]
 [browser_webconsole_split_escape_key.js]
 [browser_webconsole_split_focus.js]
 [browser_webconsole_split_persist.js]
--- a/devtools/server/actors/styleeditor.js
+++ b/devtools/server/actors/styleeditor.js
@@ -27,251 +27,16 @@ var TRANSITION_RULE = "\
 transition-duration: " + TRANSITION_DURATION_MS + "ms !important; \
 transition-delay: 0ms !important;\
 transition-timing-function: ease-out !important;\
 transition-property: all !important;\
 }";
 
 var LOAD_ERROR = "error-load";
 
-types.addActorType("old-stylesheet");
-
-/**
- * Creates a StyleEditorActor. StyleEditorActor provides remote access to the
- * stylesheets of a document.
- */
-var StyleEditorActor = exports.StyleEditorActor = protocol.ActorClass({
-  typeName: "styleeditor",
-
-  /**
-   * The window we work with, taken from the parent actor.
-   */
-  get window() {
-    return this.parentActor.window;
-  },
-
-  /**
-   * The current content document of the window we work with.
-   */
-  get document() {
-    return this.window.document;
-  },
-
-  events: {
-    "document-load" : {
-      type: "documentLoad",
-      styleSheets: Arg(0, "array:old-stylesheet")
-    }
-  },
-
-  form: function ()
-  {
-    return { actor: this.actorID };
-  },
-
-  initialize: function (conn, tabActor) {
-    protocol.Actor.prototype.initialize.call(this, null);
-
-    this.parentActor = tabActor;
-
-    // keep a map of sheets-to-actors so we don't create two actors for one sheet
-    this._sheets = new Map();
-  },
-
-  /**
-   * Destroy the current StyleEditorActor instance.
-   */
-  destroy: function ()
-  {
-    this._sheets.clear();
-  },
-
-  /**
-   * Called by client when target navigates to a new document.
-   * Adds load listeners to document.
-   */
-  newDocument: method(function () {
-    // delete previous document's actors
-    this._clearStyleSheetActors();
-
-    // Note: listening for load won't be necessary once
-    // https://bugzilla.mozilla.org/show_bug.cgi?id=839103 is fixed
-    if (this.document.readyState == "complete") {
-      this._onDocumentLoaded();
-    }
-    else {
-      this.window.addEventListener("load", this._onDocumentLoaded, false);
-    }
-    return {};
-  }),
-
-  /**
-   * Event handler for document loaded event. Add actor for each stylesheet
-   * and send an event notifying of the load
-   */
-  _onDocumentLoaded: function (event) {
-    if (event) {
-      this.window.removeEventListener("load", this._onDocumentLoaded, false);
-    }
-
-    let documents = [this.document];
-    var forms = [];
-    for (let doc of documents) {
-      let sheetForms = this._addStyleSheets(doc.styleSheets);
-      forms = forms.concat(sheetForms);
-      // Recursively handle style sheets of the documents in iframes.
-      for (let iframe of doc.getElementsByTagName("iframe")) {
-        documents.push(iframe.contentDocument);
-      }
-    }
-
-    events.emit(this, "document-load", forms);
-  },
-
-  /**
-   * Add all the stylesheets to the map and create an actor for each one
-   * if not already created. Send event that there are new stylesheets.
-   *
-   * @param {[DOMStyleSheet]} styleSheets
-   *        Stylesheets to add
-   * @return {[object]}
-   *         Array of actors for each StyleSheetActor created
-   */
-  _addStyleSheets: function (styleSheets)
-  {
-    let sheets = [];
-    for (let i = 0; i < styleSheets.length; i++) {
-      let styleSheet = styleSheets[i];
-      sheets.push(styleSheet);
-
-      // Get all sheets, including imported ones
-      let imports = this._getImported(styleSheet);
-      sheets = sheets.concat(imports);
-    }
-    let actors = sheets.map(this._createStyleSheetActor.bind(this));
-
-    return actors;
-  },
-
-  /**
-   * Create a new actor for a style sheet, if it hasn't already been created.
-   *
-   * @param  {DOMStyleSheet} styleSheet
-   *         The style sheet to create an actor for.
-   * @return {StyleSheetActor}
-   *         The actor for this style sheet
-   */
-  _createStyleSheetActor: function (styleSheet)
-  {
-    if (this._sheets.has(styleSheet)) {
-      return this._sheets.get(styleSheet);
-    }
-    let actor = new OldStyleSheetActor(styleSheet, this);
-
-    this.manage(actor);
-    this._sheets.set(styleSheet, actor);
-
-    return actor;
-  },
-
-  /**
-   * Get all the stylesheets @imported from a stylesheet.
-   *
-   * @param  {DOMStyleSheet} styleSheet
-   *         Style sheet to search
-   * @return {array}
-   *         All the imported stylesheets
-   */
-  _getImported: function (styleSheet) {
-    let imported = [];
-
-    for (let i = 0; i < styleSheet.cssRules.length; i++) {
-      let rule = styleSheet.cssRules[i];
-      if (rule.type == Ci.nsIDOMCSSRule.IMPORT_RULE) {
-        // Associated styleSheet may be null if it has already been seen due to
-        // duplicate @imports for the same URL.
-        if (!rule.styleSheet) {
-          continue;
-        }
-        imported.push(rule.styleSheet);
-
-        // recurse imports in this stylesheet as well
-        imported = imported.concat(this._getImported(rule.styleSheet));
-      }
-      else if (rule.type != Ci.nsIDOMCSSRule.CHARSET_RULE) {
-        // @import rules must precede all others except @charset
-        break;
-      }
-    }
-    return imported;
-  },
-
-  /**
-   * Clear all the current stylesheet actors in map.
-   */
-  _clearStyleSheetActors: function () {
-    for (let actor in this._sheets) {
-      this.unmanage(this._sheets[actor]);
-    }
-    this._sheets.clear();
-  },
-
-  /**
-   * Create a new style sheet in the document with the given text.
-   * Return an actor for it.
-   *
-   * @param  {object} request
-   *         Debugging protocol request object, with 'text property'
-   * @return {object}
-   *         Object with 'styelSheet' property for form on new actor.
-   */
-  newStyleSheet: method(function (text) {
-    let parent = this.document.documentElement;
-    let style = this.document.createElementNS("http://www.w3.org/1999/xhtml", "style");
-    style.setAttribute("type", "text/css");
-
-    if (text) {
-      style.appendChild(this.document.createTextNode(text));
-    }
-    parent.appendChild(style);
-
-    let actor = this._createStyleSheetActor(style.sheet);
-    return actor;
-  }, {
-    request: { text: Arg(0, "string") },
-    response: { styleSheet: RetVal("old-stylesheet") }
-  })
-});
-
-/**
- * The corresponding Front object for the StyleEditorActor.
- */
-var StyleEditorFront = protocol.FrontClass(StyleEditorActor, {
-  initialize: function (client, tabForm) {
-    protocol.Front.prototype.initialize.call(this, client);
-    this.actorID = tabForm.styleEditorActor;
-    this.manage(this);
-  },
-
-  getStyleSheets: function () {
-    let deferred = promise.defer();
-
-    events.once(this, "document-load", (styleSheets) => {
-      deferred.resolve(styleSheets);
-    });
-    this.newDocument();
-
-    return deferred.promise;
-  },
-
-  addStyleSheet: function (text) {
-    return this.newStyleSheet(text);
-  }
-});
-
 /**
  * A StyleSheetActor represents a stylesheet on the server.
  */
 var OldStyleSheetActor = protocol.ActorClass({
   typeName: "old-stylesheet",
 
   events: {
     "property-change" : {
@@ -645,27 +410,492 @@ var OldStyleSheetFront = protocol.FrontC
   get styleSheetIndex() {
     return this._form.styleSheetIndex;
   },
   get ruleCount() {
     return this._form.ruleCount;
   }
 });
 
+/**
+ * Creates a StyleEditorActor. StyleEditorActor provides remote access to the
+ * stylesheets of a document.
+ */
+var StyleEditorActor = exports.StyleEditorActor = protocol.ActorClass({
+  typeName: "styleeditor",
+
+  /**
+   * The window we work with, taken from the parent actor.
+   */
+  get window() {
+    return this.parentActor.window;
+  },
+
+  /**
+   * The current content document of the window we work with.
+   */
+  get document() {
+    return this.window.document;
+  },
+
+  events: {
+    "document-load" : {
+      type: "documentLoad",
+      styleSheets: Arg(0, "array:old-stylesheet")
+    }
+  },
+
+  form: function ()
+  {
+    return { actor: this.actorID };
+  },
+
+  initialize: function (conn, tabActor) {
+    protocol.Actor.prototype.initialize.call(this, null);
+
+    this.parentActor = tabActor;
+
+    // keep a map of sheets-to-actors so we don't create two actors for one sheet
+    this._sheets = new Map();
+  },
+
+  /**
+   * Destroy the current StyleEditorActor instance.
+   */
+  destroy: function ()
+  {
+    this._sheets.clear();
+  },
+
+  /**
+   * Called by client when target navigates to a new document.
+   * Adds load listeners to document.
+   */
+  newDocument: method(function () {
+    // delete previous document's actors
+    this._clearStyleSheetActors();
+
+    // Note: listening for load won't be necessary once
+    // https://bugzilla.mozilla.org/show_bug.cgi?id=839103 is fixed
+    if (this.document.readyState == "complete") {
+      this._onDocumentLoaded();
+    }
+    else {
+      this.window.addEventListener("load", this._onDocumentLoaded, false);
+    }
+    return {};
+  }),
+
+  /**
+   * Event handler for document loaded event. Add actor for each stylesheet
+   * and send an event notifying of the load
+   */
+  _onDocumentLoaded: function (event) {
+    if (event) {
+      this.window.removeEventListener("load", this._onDocumentLoaded, false);
+    }
+
+    let documents = [this.document];
+    var forms = [];
+    for (let doc of documents) {
+      let sheetForms = this._addStyleSheets(doc.styleSheets);
+      forms = forms.concat(sheetForms);
+      // Recursively handle style sheets of the documents in iframes.
+      for (let iframe of doc.getElementsByTagName("iframe")) {
+        documents.push(iframe.contentDocument);
+      }
+    }
+
+    events.emit(this, "document-load", forms);
+  },
+
+  /**
+   * Add all the stylesheets to the map and create an actor for each one
+   * if not already created. Send event that there are new stylesheets.
+   *
+   * @param {[DOMStyleSheet]} styleSheets
+   *        Stylesheets to add
+   * @return {[object]}
+   *         Array of actors for each StyleSheetActor created
+   */
+  _addStyleSheets: function (styleSheets)
+  {
+    let sheets = [];
+    for (let i = 0; i < styleSheets.length; i++) {
+      let styleSheet = styleSheets[i];
+      sheets.push(styleSheet);
+
+      // Get all sheets, including imported ones
+      let imports = this._getImported(styleSheet);
+      sheets = sheets.concat(imports);
+    }
+    let actors = sheets.map(this._createStyleSheetActor.bind(this));
+
+    return actors;
+  },
+
+  /**
+   * Create a new actor for a style sheet, if it hasn't already been created.
+   *
+   * @param  {DOMStyleSheet} styleSheet
+   *         The style sheet to create an actor for.
+   * @return {StyleSheetActor}
+   *         The actor for this style sheet
+   */
+  _createStyleSheetActor: function (styleSheet)
+  {
+    if (this._sheets.has(styleSheet)) {
+      return this._sheets.get(styleSheet);
+    }
+    let actor = new OldStyleSheetActor(styleSheet, this);
+
+    this.manage(actor);
+    this._sheets.set(styleSheet, actor);
+
+    return actor;
+  },
+
+  /**
+   * Get all the stylesheets @imported from a stylesheet.
+   *
+   * @param  {DOMStyleSheet} styleSheet
+   *         Style sheet to search
+   * @return {array}
+   *         All the imported stylesheets
+   */
+  _getImported: function (styleSheet) {
+    let imported = [];
+
+    for (let i = 0; i < styleSheet.cssRules.length; i++) {
+      let rule = styleSheet.cssRules[i];
+      if (rule.type == Ci.nsIDOMCSSRule.IMPORT_RULE) {
+        // Associated styleSheet may be null if it has already been seen due to
+        // duplicate @imports for the same URL.
+        if (!rule.styleSheet) {
+          continue;
+        }
+        imported.push(rule.styleSheet);
+
+        // recurse imports in this stylesheet as well
+        imported = imported.concat(this._getImported(rule.styleSheet));
+      }
+      else if (rule.type != Ci.nsIDOMCSSRule.CHARSET_RULE) {
+        // @import rules must precede all others except @charset
+        break;
+      }
+    }
+    return imported;
+  },
+
+  /**
+   * Clear all the current stylesheet actors in map.
+   */
+  _clearStyleSheetActors: function () {
+    for (let actor in this._sheets) {
+      this.unmanage(this._sheets[actor]);
+    }
+    this._sheets.clear();
+  },
+
+  /**
+   * Create a new style sheet in the document with the given text.
+   * Return an actor for it.
+   *
+   * @param  {object} request
+   *         Debugging protocol request object, with 'text property'
+   * @return {object}
+   *         Object with 'styelSheet' property for form on new actor.
+   */
+  newStyleSheet: method(function (text) {
+    let parent = this.document.documentElement;
+    let style = this.document.createElementNS("http://www.w3.org/1999/xhtml", "style");
+    style.setAttribute("type", "text/css");
+
+    if (text) {
+      style.appendChild(this.document.createTextNode(text));
+    }
+    parent.appendChild(style);
+
+    let actor = this._createStyleSheetActor(style.sheet);
+    return actor;
+  }, {
+    request: { text: Arg(0, "string") },
+    response: { styleSheet: RetVal("old-stylesheet") }
+  })
+});
+
+/**
+ * The corresponding Front object for the StyleEditorActor.
+ */
+var StyleEditorFront = protocol.FrontClass(StyleEditorActor, {
+  initialize: function (client, tabForm) {
+    protocol.Front.prototype.initialize.call(this, client);
+    this.actorID = tabForm.styleEditorActor;
+    this.manage(this);
+  },
+
+  getStyleSheets: function () {
+    let deferred = promise.defer();
+
+    events.once(this, "document-load", (styleSheets) => {
+      deferred.resolve(styleSheets);
+    });
+    this.newDocument();
+
+    return deferred.promise;
+  },
+
+  addStyleSheet: function (text) {
+    return this.newStyleSheet(text);
+  }
+});
+
+/**
+ * Creates a StyleEditorActor. StyleEditorActor provides remote access to the
+ * stylesheets of a document.
+ */
+var StyleEditorActor = exports.StyleEditorActor = protocol.ActorClass({
+  typeName: "styleeditor",
+
+  /**
+   * The window we work with, taken from the parent actor.
+   */
+  get window() {
+    return this.parentActor.window;
+  },
+
+  /**
+   * The current content document of the window we work with.
+   */
+  get document() {
+    return this.window.document;
+  },
+
+  events: {
+    "document-load" : {
+      type: "documentLoad",
+      styleSheets: Arg(0, "array:old-stylesheet")
+    }
+  },
+
+  form: function ()
+  {
+    return { actor: this.actorID };
+  },
+
+  initialize: function (conn, tabActor) {
+    protocol.Actor.prototype.initialize.call(this, null);
+
+    this.parentActor = tabActor;
+
+    // keep a map of sheets-to-actors so we don't create two actors for one sheet
+    this._sheets = new Map();
+  },
+
+  /**
+   * Destroy the current StyleEditorActor instance.
+   */
+  destroy: function ()
+  {
+    this._sheets.clear();
+  },
+
+  /**
+   * Called by client when target navigates to a new document.
+   * Adds load listeners to document.
+   */
+  newDocument: method(function () {
+    // delete previous document's actors
+    this._clearStyleSheetActors();
+
+    // Note: listening for load won't be necessary once
+    // https://bugzilla.mozilla.org/show_bug.cgi?id=839103 is fixed
+    if (this.document.readyState == "complete") {
+      this._onDocumentLoaded();
+    }
+    else {
+      this.window.addEventListener("load", this._onDocumentLoaded, false);
+    }
+    return {};
+  }),
+
+  /**
+   * Event handler for document loaded event. Add actor for each stylesheet
+   * and send an event notifying of the load
+   */
+  _onDocumentLoaded: function (event) {
+    if (event) {
+      this.window.removeEventListener("load", this._onDocumentLoaded, false);
+    }
+
+    let documents = [this.document];
+    var forms = [];
+    for (let doc of documents) {
+      let sheetForms = this._addStyleSheets(doc.styleSheets);
+      forms = forms.concat(sheetForms);
+      // Recursively handle style sheets of the documents in iframes.
+      for (let iframe of doc.getElementsByTagName("iframe")) {
+        documents.push(iframe.contentDocument);
+      }
+    }
+
+    events.emit(this, "document-load", forms);
+  },
+
+  /**
+   * Add all the stylesheets to the map and create an actor for each one
+   * if not already created. Send event that there are new stylesheets.
+   *
+   * @param {[DOMStyleSheet]} styleSheets
+   *        Stylesheets to add
+   * @return {[object]}
+   *         Array of actors for each StyleSheetActor created
+   */
+  _addStyleSheets: function (styleSheets)
+  {
+    let sheets = [];
+    for (let i = 0; i < styleSheets.length; i++) {
+      let styleSheet = styleSheets[i];
+      sheets.push(styleSheet);
+
+      // Get all sheets, including imported ones
+      let imports = this._getImported(styleSheet);
+      sheets = sheets.concat(imports);
+    }
+    let actors = sheets.map(this._createStyleSheetActor.bind(this));
+
+    return actors;
+  },
+
+  /**
+   * Create a new actor for a style sheet, if it hasn't already been created.
+   *
+   * @param  {DOMStyleSheet} styleSheet
+   *         The style sheet to create an actor for.
+   * @return {StyleSheetActor}
+   *         The actor for this style sheet
+   */
+  _createStyleSheetActor: function (styleSheet)
+  {
+    if (this._sheets.has(styleSheet)) {
+      return this._sheets.get(styleSheet);
+    }
+    let actor = new OldStyleSheetActor(styleSheet, this);
+
+    this.manage(actor);
+    this._sheets.set(styleSheet, actor);
+
+    return actor;
+  },
+
+  /**
+   * Get all the stylesheets @imported from a stylesheet.
+   *
+   * @param  {DOMStyleSheet} styleSheet
+   *         Style sheet to search
+   * @return {array}
+   *         All the imported stylesheets
+   */
+  _getImported: function (styleSheet) {
+   let imported = [];
+
+   for (let i = 0; i < styleSheet.cssRules.length; i++) {
+      let rule = styleSheet.cssRules[i];
+      if (rule.type == Ci.nsIDOMCSSRule.IMPORT_RULE) {
+        // Associated styleSheet may be null if it has already been seen due to
+        // duplicate @imports for the same URL.
+        if (!rule.styleSheet) {
+          continue;
+        }
+        imported.push(rule.styleSheet);
+
+        // recurse imports in this stylesheet as well
+        imported = imported.concat(this._getImported(rule.styleSheet));
+      }
+      else if (rule.type != Ci.nsIDOMCSSRule.CHARSET_RULE) {
+        // @import rules must precede all others except @charset
+        break;
+      }
+    }
+    return imported;
+  },
+
+  /**
+   * Clear all the current stylesheet actors in map.
+   */
+  _clearStyleSheetActors: function () {
+    for (let actor in this._sheets) {
+      this.unmanage(this._sheets[actor]);
+    }
+    this._sheets.clear();
+  },
+
+  /**
+   * Create a new style sheet in the document with the given text.
+   * Return an actor for it.
+   *
+   * @param  {object} request
+   *         Debugging protocol request object, with 'text property'
+   * @return {object}
+   *         Object with 'styelSheet' property for form on new actor.
+   */
+  newStyleSheet: method(function (text) {
+    let parent = this.document.documentElement;
+    let style = this.document.createElementNS("http://www.w3.org/1999/xhtml", "style");
+    style.setAttribute("type", "text/css");
+
+    if (text) {
+      style.appendChild(this.document.createTextNode(text));
+    }
+    parent.appendChild(style);
+
+    let actor = this._createStyleSheetActor(style.sheet);
+    return actor;
+  }, {
+    request: { text: Arg(0, "string") },
+    response: { styleSheet: RetVal("old-stylesheet") }
+  })
+});
+
+/**
+ * The corresponding Front object for the StyleEditorActor.
+ */
+var StyleEditorFront = protocol.FrontClass(StyleEditorActor, {
+  initialize: function (client, tabForm) {
+    protocol.Front.prototype.initialize.call(this, client);
+    this.actorID = tabForm.styleEditorActor;
+    this.manage(this);
+  },
+
+  getStyleSheets: function () {
+    let deferred = promise.defer();
+
+    events.once(this, "document-load", (styleSheets) => {
+      deferred.resolve(styleSheets);
+    });
+    this.newDocument();
+
+    return deferred.promise;
+  },
+
+  addStyleSheet: function (text) {
+    return this.newStyleSheet(text);
+  }
+});
+
 XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 });
 
 exports.StyleEditorActor = StyleEditorActor;
 exports.StyleEditorFront = StyleEditorFront;
 
 exports.OldStyleSheetActor = OldStyleSheetActor;
 exports.OldStyleSheetFront = OldStyleSheetFront;
 
-
 /**
  * Normalize multiple relative paths towards the base paths on the right.
  */
 function normalize(...aURLs) {
   let base = Services.io.newURI(aURLs.pop(), null, null);
   let url;
   while ((url = aURLs.pop())) {
     base = Services.io.newURI(url, null, base);
--- a/dom/base/ResponsiveImageSelector.cpp
+++ b/dom/base/ResponsiveImageSelector.cpp
@@ -240,22 +240,18 @@ bool
 ResponsiveImageSelector::SetSizesFromDescriptor(const nsAString & aSizes)
 {
   ClearSelectedCandidate();
   mSizeQueries.Clear();
   mSizeValues.Clear();
 
   nsCSSParser cssParser;
 
-  if (!cssParser.ParseSourceSizeList(aSizes, nullptr, 0,
-                                     mSizeQueries, mSizeValues, true)) {
-    return false;
-  }
-
-  return mSizeQueries.Length() > 0;
+  return cssParser.ParseSourceSizeList(aSizes, nullptr, 0,
+                                       mSizeQueries, mSizeValues, true);
 }
 
 void
 ResponsiveImageSelector::AppendCandidateIfUnique(const ResponsiveImageCandidate & aCandidate)
 {
   int numCandidates = mCandidates.Length();
 
   // With the exception of Default, which should not be added until we are done
--- a/dom/base/test/chrome.ini
+++ b/dom/base/test/chrome.ini
@@ -13,16 +13,17 @@ support-files =
 [test_domrequesthelper.xul]
 [test_url.xul]
 [test_console.xul]
 [test_navigator_resolve_identity_xrays.xul]
 support-files = file_navigator_resolve_identity_xrays.xul
 [test_sendQueryContentAndSelectionSetEvent.html]
 [test_bug1016960.html]
 [test_copypaste.xul]
+subsuite = clipboard
 [test_messagemanager_principal.html]
 [test_messagemanager_send_principal.html]
 skip-if = buildapp == 'mulet'
 [test_bug945152.html]
 run-if = os == 'linux'
 [test_bug1008126.html]
 run-if = os == 'linux'
 [test_sandboxed_blob_uri.html]
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -307,17 +307,19 @@ skip-if = buildapp == 'mulet'
 [test_base.xhtml]
 [test_blob_fragment_and_query.html]
 [test_blobconstructor.html]
 [test_bug5141.html]
 [test_bug28293.html]
 [test_bug28293.xhtml]
 [test_bug51034.html]
 [test_bug116083.html]
+subsuite = clipboard
 [test_bug166235.html]
+subsuite = clipboard
 skip-if = buildapp == 'b2g' || toolkit == 'android' # b2g(clipboard undefined) b2g-debug(clipboard undefined) b2g-desktop(clipboard undefined)
 [test_bug199959.html]
 [test_bug218236.html]
 [test_bug218277.html]
 [test_bug238409.html]
 [test_bug254337.html]
 [test_bug270145.xhtml]
 [test_bug276037-1.html]
@@ -673,20 +675,23 @@ skip-if = buildapp == 'b2g'
 [test_change_policy.html]
 skip-if = buildapp == 'b2g' #no ssl support
 [test_classList.html]
 [test_clearTimeoutIntervalNoArg.html]
 [test_consoleEmptyStack.html]
 [test_constructor-assignment.html]
 [test_constructor.html]
 [test_copyimage.html]
+subsuite = clipboard
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') || (toolkit != 'cocoa' && toolkit != 'gonk' && toolkit != 'gtk2' && toolkit != 'gtk3' && toolkit != 'windows') #b2g-desktop(Bug 931116, b2g desktop specific, initial triage)
 [test_copypaste.html]
+subsuite = clipboard
 skip-if = buildapp == 'b2g' || toolkit == 'android' #bug 904183 # b2g(clipboard undefined) b2g-debug(clipboard undefined) b2g-desktop(clipboard undefined)
 [test_copypaste.xhtml]
+subsuite = clipboard
 skip-if = buildapp == 'b2g' || toolkit == 'android' #bug 904183 # b2g(bug 904183) b2g-debug(bug 904183) b2g-desktop(bug 904183)
 [test_createHTMLDocument.html]
 [test_declare_stylesheet_obsolete.html]
 [test_dialogArguments.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s # showmodaldialog
 [test_document.all_iteration.html]
 [test_document.all_unqualified.html]
 [test_document_constructor.html]
--- a/dom/browser-element/mochitest/mochitest.ini
+++ b/dom/browser-element/mochitest/mochitest.ini
@@ -181,16 +181,17 @@ skip-if = buildapp == 'b2g'
 [test_browserElement_inproc_Close.html]
 [test_browserElement_inproc_CloseApp.html]
 skip-if = toolkit == 'android' || buildapp == 'b2g' # android(FAILS, bug 796982) androidx86(FAILS, bug 796982)
 [test_browserElement_inproc_CloseFromOpener.html]
 skip-if = buildapp == 'b2g'
 [test_browserElement_inproc_ContextmenuEvents.html]
 [test_browserElement_inproc_CookiesNotThirdParty.html]
 [test_browserElement_inproc_CopyPaste.html]
+subsuite = clipboard
 skip-if = (os == "android") # Disabled on Android, see bug 1230421
 [test_browserElement_inproc_DOMRequestError.html]
 [test_browserElement_inproc_DataURI.html]
 [test_browserElement_inproc_DisallowEmbedAppsInOOP.html]
 skip-if = buildapp != 'mulet'
 [test_browserElement_inproc_DocumentFirstPaint.html]
 [test_browserElement_inproc_Download.html]
 disabled = bug 1022281
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -3577,26 +3577,41 @@ struct MOZ_STACK_CLASS CanvasBidiProcess
                               mDrawTarget);
       inlineCoord += textRunMetrics.mAdvanceWidth;
       // old code was:
       //   point.x += width * mAppUnitsPerDevPixel;
       // TODO: restore this if/when we move to fractional coords
       // throughout the text layout process
     }
 
+    mCtx->EnsureTarget();
+
+    // If the operation is 'fill' with a simple color, we defer to gfxTextRun
+    // which will handle color/svg-in-ot fonts appropriately. Such fonts will
+    // not render well via the code below.
+    if (mOp == CanvasRenderingContext2D::TextDrawOperation::FILL &&
+        mState->StyleIsColor(CanvasRenderingContext2D::Style::FILL)) {
+      RefPtr<gfxContext> thebes =
+        gfxContext::ForDrawTargetWithTransform(mCtx->mTarget);
+      nscolor fill = mState->colorStyles[CanvasRenderingContext2D::Style::FILL];
+      thebes->SetColor(Color::FromABGR(fill));
+      gfxTextRun::DrawParams params(thebes);
+      mTextRun->Draw(gfxTextRun::Range(mTextRun.get()), point, params);
+      return;
+    }
+
     uint32_t numRuns;
     const gfxTextRun::GlyphRun *runs = mTextRun->GetGlyphRuns(&numRuns);
     const int32_t appUnitsPerDevUnit = mAppUnitsPerDevPixel;
     const double devUnitsPerAppUnit = 1.0/double(appUnitsPerDevUnit);
     Point baselineOrigin =
       Point(point.x * devUnitsPerAppUnit, point.y * devUnitsPerAppUnit);
 
     float advanceSum = 0;
 
-    mCtx->EnsureTarget();
     for (uint32_t c = 0; c < numRuns; c++) {
       gfxFont *font = runs[c].mFont;
 
       bool verticalFont =
         runs[c].mOrientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT;
 
       const float& baselineOriginInline =
         verticalFont ? baselineOrigin.y : baselineOrigin.x;
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -490,19 +490,20 @@ WebGLContext::ValidateUniformArraySetter
         return false;
 
     if (!loc->ValidateSizeAndType(setterElemSize, setterType, this, funcName))
         return false;
 
     if (!loc->ValidateArrayLength(setterElemSize, setterArraySize, this, funcName))
         return false;
 
+    MOZ_ASSERT((size_t)loc->mActiveInfo->mElemCount > loc->mArrayIndex);
+    size_t uniformElemCount = loc->mActiveInfo->mElemCount - loc->mArrayIndex;
     *out_rawLoc = loc->mLoc;
-    *out_numElementsToUpload = std::min((size_t)loc->mActiveInfo->mElemCount,
-                                        setterArraySize / setterElemSize);
+    *out_numElementsToUpload = std::min(uniformElemCount, setterArraySize / setterElemSize);
     return true;
 }
 
 bool
 WebGLContext::ValidateUniformMatrixArraySetter(WebGLUniformLocation* loc,
                                                uint8_t setterCols,
                                                uint8_t setterRows,
                                                GLenum setterType,
@@ -524,19 +525,21 @@ WebGLContext::ValidateUniformMatrixArray
         return false;
 
     if (!loc->ValidateArrayLength(setterElemSize, setterArraySize, this, funcName))
         return false;
 
     if (!ValidateUniformMatrixTranspose(setterTranspose, funcName))
         return false;
 
+    MOZ_ASSERT((size_t)loc->mActiveInfo->mElemCount > loc->mArrayIndex);
+    size_t uniformElemCount = loc->mActiveInfo->mElemCount - loc->mArrayIndex;
     *out_rawLoc = loc->mLoc;
-    *out_numElementsToUpload = std::min((size_t)loc->mActiveInfo->mElemCount,
-                                        setterArraySize / setterElemSize);
+    *out_numElementsToUpload = std::min(uniformElemCount, setterArraySize / setterElemSize);
+
     return true;
 }
 
 bool
 WebGLContext::ValidateAttribIndex(GLuint index, const char* info)
 {
     bool valid = (index < MaxVertexAttribs());
 
--- a/dom/canvas/WebGLExtensionBase.cpp
+++ b/dom/canvas/WebGLExtensionBase.cpp
@@ -16,16 +16,18 @@ WebGLExtensionBase::WebGLExtensionBase(W
 WebGLExtensionBase::~WebGLExtensionBase()
 {
 }
 
 void
 WebGLExtensionBase::MarkLost()
 {
     mIsLost = true;
+
+    OnMarkLost();
 }
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLExtensionBase)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLExtensionBase, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLExtensionBase, Release)
 
 } // namespace mozilla
--- a/dom/canvas/WebGLExtensionDisjointTimerQuery.cpp
+++ b/dom/canvas/WebGLExtensionDisjointTimerQuery.cpp
@@ -244,11 +244,17 @@ WebGLExtensionDisjointTimerQuery::IsSupp
          gl->IsSupported(gl::GLFeature::get_query_object_i64v) &&
          gl->IsSupported(gl::GLFeature::query_counter) && // provides GL_TIMESTAMP
          gl->IsSupported(gl::GLFeature::sync); // provides glGetInteger64v
   // 'sync' provides glGetInteger64v either by supporting ARB_sync, GL3+, or GLES3+.
   // Since there are no differences between support for glGetInteger64v and support for
   // 'sync', we just piggy-back off of 'sync'.
 }
 
+void
+WebGLExtensionDisjointTimerQuery::OnMarkLost()
+{
+  mActiveQuery = nullptr;
+}
+
 IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionDisjointTimerQuery, EXT_disjoint_timer_query)
 
 } // namespace mozilla
--- a/dom/canvas/WebGLExtensions.h
+++ b/dom/canvas/WebGLExtensions.h
@@ -44,16 +44,18 @@ public:
     void MarkLost();
 
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLExtensionBase)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLExtensionBase)
 
 protected:
     virtual ~WebGLExtensionBase();
 
+    virtual void OnMarkLost() { }
+
     bool mIsLost;
 };
 
 #define DECL_WEBGL_EXTENSION_GOOP \
     virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override;
 
 #define IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionType, WebGLBindingType)\
     JSObject*                                                    \
@@ -382,16 +384,18 @@ public:
                            GLenum pname,
                            JS::MutableHandle<JS::Value> retval);
 
     static bool IsSupported(const WebGLContext*);
 
     DECL_WEBGL_EXTENSION_GOOP
 
 private:
+    virtual void OnMarkLost() override;
+
     /**
      * An active TIME_ELAPSED query participating in a begin/end block.
      */
     WebGLRefPtr<WebGLTimerQuery> mActiveQuery;
 };
 
 } // namespace mozilla
 
--- a/dom/canvas/WebGLProgram.cpp
+++ b/dom/canvas/WebGLProgram.cpp
@@ -781,18 +781,24 @@ WebGLProgram::GetUniformLocation(const n
     if (!IsLinked()) {
         mContext->ErrorInvalidOperation("getUniformLocation: `program` must be linked.");
         return nullptr;
     }
 
     const NS_LossyConvertUTF16toASCII userName(userName_wide);
 
     nsDependentCString baseUserName;
-    bool isArray;
-    size_t arrayIndex;
+    bool isArray = false;
+    // GLES 2.0.25, Section 2.10, p35
+    // If the the uniform location is an array, then the location of the first
+    // element of that array can be retrieved by either using the name of the
+    // uniform array, or the name of the uniform array appended with "[0]".
+    // The ParseName() can't recognize this rule. So always initialize
+    // arrayIndex with 0.
+    size_t arrayIndex = 0;
     if (!ParseName(userName, &baseUserName, &isArray, &arrayIndex))
         return nullptr;
 
     const WebGLActiveInfo* activeInfo;
     if (!LinkInfo()->FindUniform(baseUserName, &activeInfo))
         return nullptr;
 
     const nsCString& baseMappedName = activeInfo->mBaseMappedName;
@@ -807,17 +813,18 @@ WebGLProgram::GetUniformLocation(const n
     gl::GLContext* gl = mContext->GL();
     gl->MakeCurrent();
 
     GLint loc = gl->fGetUniformLocation(mGLName, mappedName.BeginReading());
     if (loc == -1)
         return nullptr;
 
     RefPtr<WebGLUniformLocation> locObj = new WebGLUniformLocation(mContext, LinkInfo(),
-                                                                     loc, activeInfo);
+                                                                   loc, arrayIndex,
+                                                                   activeInfo);
     return locObj.forget();
 }
 
 void
 WebGLProgram::GetUniformIndices(const dom::Sequence<nsString>& uniformNames,
                                 dom::Nullable< nsTArray<GLuint> >& retval) const
 {
     size_t count = uniformNames.Length();
--- a/dom/canvas/WebGLShaderValidator.cpp
+++ b/dom/canvas/WebGLShaderValidator.cpp
@@ -78,16 +78,19 @@ ChooseValidatorCompileOptions(const ShBu
         if (gl->Vendor() == gl::GLVendor::Intel) {
             options |= SH_EMULATE_BUILT_IN_FUNCTIONS;
         }
 
         // Work around bug 636926
         if (gl->Vendor() == gl::GLVendor::NVIDIA) {
             options |= SH_UNROLL_FOR_LOOP_WITH_SAMPLER_ARRAY_INDEX;
         }
+
+        // Work around that Mac drivers handle struct scopes incorrectly.
+        options |= SH_REGENERATE_STRUCT_NAMES;
     }
 #endif
 
     return options;
 }
 
 } // namespace webgl
 
--- a/dom/canvas/WebGLUniformLocation.cpp
+++ b/dom/canvas/WebGLUniformLocation.cpp
@@ -11,20 +11,23 @@
 #include "WebGLActiveInfo.h"
 #include "WebGLContext.h"
 #include "WebGLProgram.h"
 
 namespace mozilla {
 
 WebGLUniformLocation::WebGLUniformLocation(WebGLContext* webgl,
                                            const webgl::LinkedProgramInfo* linkInfo,
-                                           GLuint loc, const WebGLActiveInfo* activeInfo)
+                                           GLuint loc,
+                                           size_t arrayIndex,
+                                           const WebGLActiveInfo* activeInfo)
     : WebGLContextBoundObject(webgl)
     , mLinkInfo(linkInfo)
     , mLoc(loc)
+    , mArrayIndex(arrayIndex)
     , mActiveInfo(activeInfo)
 { }
 
 WebGLUniformLocation::~WebGLUniformLocation()
 { }
 
 bool
 WebGLUniformLocation::ValidateForProgram(WebGLProgram* prog, WebGLContext* webgl,
--- a/dom/canvas/WebGLUniformLocation.h
+++ b/dom/canvas/WebGLUniformLocation.h
@@ -36,20 +36,21 @@ public:
     virtual JSObject* WrapObject(JSContext* js, JS::Handle<JSObject*> givenProto) override;
 
     WebGLContext* GetParentObject() const {
         return mContext;
     }
 
     const WeakPtr<const webgl::LinkedProgramInfo> mLinkInfo;
     const GLuint mLoc;
+    const size_t mArrayIndex;
     const WebGLActiveInfo* const mActiveInfo;
 
     WebGLUniformLocation(WebGLContext* webgl, const webgl::LinkedProgramInfo* linkInfo,
-                         GLuint loc, const WebGLActiveInfo* activeInfo);
+                         GLuint loc, size_t arrayIndex, const WebGLActiveInfo* activeInfo);
 
     bool ValidateForProgram(WebGLProgram* prog, WebGLContext* webgl,
                             const char* funcName) const;
     bool ValidateSamplerSetter(GLint value, WebGLContext* webgl,
                                const char* funcName) const;
     bool ValidateSizeAndType(uint8_t setterElemSize, GLenum setterType,
                              WebGLContext* webgl, const char* funcName) const;
     bool ValidateArrayLength(uint8_t setterElemSize, size_t setterArraySize,
--- a/dom/canvas/test/webgl-conf/generated-mochitest.ini
+++ b/dom/canvas/test/webgl-conf/generated-mochitest.ini
@@ -2657,17 +2657,16 @@ fail-if = (os == 'mac' && os_version == 
 [generated/test_conformance__glsl__misc__large-loop-compile.html]
 fail-if = (os == 'win' && os_version == '5.1')
 [generated/test_conformance__glsl__misc__non-ascii-comments.vert.html]
 [generated/test_conformance__glsl__misc__non-ascii.vert.html]
 [generated/test_conformance__glsl__misc__re-compile-re-link.html]
 fail-if = (os == 'android' && android_version == '10')
 [generated/test_conformance__glsl__misc__shader-precision-format-obeyed.html]
 [generated/test_conformance__glsl__misc__shader-struct-scope.html]
-fail-if = (os == 'mac')
 [generated/test_conformance__glsl__misc__shader-uniform-packing-restrictions.html]
 skip-if = (os == 'android')
 [generated/test_conformance__glsl__misc__shader-varying-packing-restrictions.html]
 [generated/test_conformance__glsl__misc__shader-with-256-character-define.html]
 [generated/test_conformance__glsl__misc__shader-with-256-character-identifier.frag.html]
 [generated/test_conformance__glsl__misc__shader-with-257-character-define.html]
 [generated/test_conformance__glsl__misc__shader-with-257-character-identifier.frag.html]
 [generated/test_conformance__glsl__misc__shader-with-_webgl-identifier.vert.html]
@@ -2745,17 +2744,16 @@ fail-if = 1
 [generated/test_conformance__glsl__misc__shaders-with-name-conflicts.html]
 [generated/test_conformance__glsl__misc__shaders-with-uniform-structs.html]
 [generated/test_conformance__glsl__misc__shaders-with-varyings.html]
 [generated/test_conformance__glsl__misc__shared.html]
 [generated/test_conformance__glsl__misc__struct-equals.html]
 [generated/test_conformance__glsl__misc__struct-mixed-array-declarators.html]
 [generated/test_conformance__glsl__misc__struct-nesting-exceeds-maximum.html]
 [generated/test_conformance__glsl__misc__struct-nesting-of-variable-names.html]
-fail-if = (os == 'mac' && os_version == '10.10')
 [generated/test_conformance__glsl__misc__struct-nesting-under-maximum.html]
 [generated/test_conformance__glsl__misc__struct-specifiers-in-uniforms.html]
 [generated/test_conformance__glsl__misc__struct-unary-operators.html]
 [generated/test_conformance__glsl__misc__ternary-operators-in-global-initializers.html]
 [generated/test_conformance__glsl__misc__ternary-operators-in-initializers.html]
 [generated/test_conformance__glsl__misc__uniform-location-length-limits.html]
 [generated/test_conformance__glsl__reserved___webgl_field.vert.html]
 [generated/test_conformance__glsl__reserved___webgl_function.vert.html]
--- a/dom/canvas/test/webgl-conf/mochitest-errata.ini
+++ b/dom/canvas/test/webgl-conf/mochitest-errata.ini
@@ -400,19 +400,16 @@ skip-if = (os == 'linux')
 skip-if = (os == 'mac')
 [generated/test_conformance__canvas__viewport-unchanged-upon-resize.html]
 # New OSX r7 machines and 10.10.5 is causing perma failure (bug 1216549)
 skip-if = (os == 'mac')
 [generated/test_conformance__rendering__multisample-corruption.html]
 # application crashed [@ gldAttachDrawable + 0x9e0]. Also crash on Android.
 skip-if = (os == 'mac') || (os == 'android')
 
-[generated/test_conformance__glsl__misc__shader-struct-scope.html]
-fail-if = (os == 'mac')
-
 [generated/test_conformance__extensions__oes-vertex-array-object.html]
 # 10.6 crash:
 # PROCESS-CRASH | dom/canvas/test/webgl-conf/generated/test_conformance__extensions__oes-vertex-array-object.html | application crashed [@ gleRunVertexSubmitImmediate + 0xf24]
 skip-if = (os == 'mac' && os_version == '10.6')
 
 ####################
 # 10.6
 [generated/test_conformance__glsl__constructors__glsl-construct-mat3.html]
@@ -434,21 +431,16 @@ fail-if = (os == 'mac' && os_version == 
 # 10.8
 [generated/test_conformance__glsl__functions__glsl-function-smoothstep-gentype.html]
 fail-if = (os == 'mac' && os_version == '10.8')
 [generated/test_conformance__glsl__variables__gl-pointcoord.html]
 fail-if = (os == 'mac' && os_version == '10.8')
 [generated/test_conformance__limits__gl-max-texture-dimensions.html]
 fail-if = (os == 'mac' && os_version == '10.8')
 
-####################
-# 10.10
-[generated/test_conformance__glsl__misc__struct-nesting-of-variable-names.html]
-fail-if = (os == 'mac' && os_version == '10.10')
-
 ########################################################################
 ########################################################################
 # Win
 
 [generated/test_conformance__glsl__misc__large-loop-compile.html]
 fail-if = (os == 'win' && os_version == '5.1')
 [generated/test_conformance__textures__copy-tex-image-2d-formats.html]
 # Assert failure in DEBUG
--- a/dom/media/platforms/PDMFactory.cpp
+++ b/dom/media/platforms/PDMFactory.cpp
@@ -97,17 +97,17 @@ PDMFactory::EnsureInit() const
         NS_NewRunnableFunction([]() { ClearOnShutdown(&sInstance); });
       NS_DispatchToMainThread(runnable);
     }
   }
 }
 
 already_AddRefed<MediaDataDecoder>
 PDMFactory::CreateDecoder(const TrackInfo& aConfig,
-                          FlushableTaskQueue* aTaskQueue,
+                          TaskQueue* aTaskQueue,
                           MediaDataDecoderCallback* aCallback,
                           DecoderDoctorDiagnostics* aDiagnostics,
                           layers::LayersBackend aLayersBackend,
                           layers::ImageContainer* aImageContainer) const
 {
   bool isEncrypted = mEMEPDM && aConfig.mCrypto.mValid;
 
   if (isEncrypted) {
@@ -152,17 +152,17 @@ PDMFactory::CreateDecoder(const TrackInf
   }
   NS_WARNING("Unable to create a decoder, no platform found.");
   return nullptr;
 }
 
 already_AddRefed<MediaDataDecoder>
 PDMFactory::CreateDecoderWithPDM(PlatformDecoderModule* aPDM,
                                  const TrackInfo& aConfig,
-                                 FlushableTaskQueue* aTaskQueue,
+                                 TaskQueue* aTaskQueue,
                                  MediaDataDecoderCallback* aCallback,
                                  DecoderDoctorDiagnostics* aDiagnostics,
                                  layers::LayersBackend aLayersBackend,
                                  layers::ImageContainer* aImageContainer) const
 {
   MOZ_ASSERT(aPDM);
   RefPtr<MediaDataDecoder> m;
 
--- a/dom/media/platforms/PDMFactory.h
+++ b/dom/media/platforms/PDMFactory.h
@@ -26,17 +26,17 @@ public:
 
   // Factory method that creates the appropriate PlatformDecoderModule for
   // the platform we're running on. Caller is responsible for deleting this
   // instance. It's expected that there will be multiple
   // PlatformDecoderModules alive at the same time.
   // This is called on the decode task queue.
   already_AddRefed<MediaDataDecoder>
   CreateDecoder(const TrackInfo& aConfig,
-                FlushableTaskQueue* aTaskQueue,
+                TaskQueue* aTaskQueue,
                 MediaDataDecoderCallback* aCallback,
                 DecoderDoctorDiagnostics* aDiagnostics,
                 layers::LayersBackend aLayersBackend = layers::LayersBackend::LAYERS_NONE,
                 layers::ImageContainer* aImageContainer = nullptr) const;
 
   bool SupportsMimeType(const nsACString& aMimeType,
                         DecoderDoctorDiagnostics* aDiagnostics) const;
 
@@ -57,17 +57,17 @@ private:
   // Returns the first PDM in our list supporting the mimetype.
   already_AddRefed<PlatformDecoderModule>
   GetDecoder(const nsACString& aMimeType,
              DecoderDoctorDiagnostics* aDiagnostics) const;
 
   already_AddRefed<MediaDataDecoder>
   CreateDecoderWithPDM(PlatformDecoderModule* aPDM,
                        const TrackInfo& aConfig,
-                       FlushableTaskQueue* aTaskQueue,
+                       TaskQueue* aTaskQueue,
                        MediaDataDecoderCallback* aCallback,
                        DecoderDoctorDiagnostics* aDiagnostics,
                        layers::LayersBackend aLayersBackend,
                        layers::ImageContainer* aImageContainer) const;
 
   nsTArray<RefPtr<PlatformDecoderModule>> mCurrentPDMs;
   RefPtr<PlatformDecoderModule> mEMEPDM;
 
--- a/dom/media/platforms/PlatformDecoderModule.h
+++ b/dom/media/platforms/PlatformDecoderModule.h
@@ -2,17 +2,16 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #if !defined(PlatformDecoderModule_h_)
 #define PlatformDecoderModule_h_
 
-#include "FlushableTaskQueue.h"
 #include "MediaDecoderReader.h"
 #include "mozilla/MozPromise.h"
 #include "mozilla/layers/LayersTypes.h"
 #include "nsTArray.h"
 #include "mozilla/RefPtr.h"
 #include <queue>
 
 namespace mozilla {
@@ -23,17 +22,17 @@ class MediaRawData;
 class DecoderDoctorDiagnostics;
 
 namespace layers {
 class ImageContainer;
 } // namespace layers
 
 class MediaDataDecoder;
 class MediaDataDecoderCallback;
-class FlushableTaskQueue;
+class TaskQueue;
 class CDMProxy;
 
 // The PlatformDecoderModule interface is used by the MediaFormatReader to
 // abstract access to decoders provided by various
 // platforms.
 // Each platform (Windows, MacOSX, Linux, B2G etc) must implement a
 // PlatformDecoderModule to provide access to its decoders in order to get
 // decompressed H.264/AAC from the MediaFormatReader.
@@ -85,33 +84,33 @@ protected:
   // COINIT_MULTITHREADED.
   // Returns nullptr if the decoder can't be created.
   // It is safe to store a reference to aConfig.
   // This is called on the decode task queue.
   virtual already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const VideoInfo& aConfig,
                      layers::LayersBackend aLayersBackend,
                      layers::ImageContainer* aImageContainer,
-                     FlushableTaskQueue* aVideoTaskQueue,
+                     TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) = 0;
 
   // Creates an Audio decoder with the specified properties.
   // Asynchronous decoding of audio should be done in runnables dispatched to
   // aAudioTaskQueue. If the task queue isn't needed, the decoder should
   // not hold a reference to it.
   // Output and errors should be returned to the reader via aCallback.
   // Returns nullptr if the decoder can't be created.
   // On Windows the task queue's threads in have MSCOM initialized with
   // COINIT_MULTITHREADED.
   // It is safe to store a reference to aConfig.
   // This is called on the decode task queue.
   virtual already_AddRefed<MediaDataDecoder>
   CreateAudioDecoder(const AudioInfo& aConfig,
-                     FlushableTaskQueue* aAudioTaskQueue,
+                     TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) = 0;
 };
 
 // A callback used by MediaDataDecoder to return output/errors to the
 // MediaFormatReader.
 // Implementation is threadsafe, and can be called on any thread.
 class MediaDataDecoderCallback {
--- a/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp
@@ -21,47 +21,47 @@ AgnosticDecoderModule::SupportsMimeType(
     VorbisDataDecoder::IsVorbis(aMimeType) ||
     WaveDataDecoder::IsWave(aMimeType);
 }
 
 already_AddRefed<MediaDataDecoder>
 AgnosticDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig,
                                           layers::LayersBackend aLayersBackend,
                                           layers::ImageContainer* aImageContainer,
-                                          FlushableTaskQueue* aVideoTaskQueue,
+                                          TaskQueue* aTaskQueue,
                                           MediaDataDecoderCallback* aCallback,
                                           DecoderDoctorDiagnostics* aDiagnostics)
 {
   RefPtr<MediaDataDecoder> m;
 
   if (VPXDecoder::IsVPX(aConfig.mMimeType)) {
     m = new VPXDecoder(*aConfig.GetAsVideoInfo(),
                        aImageContainer,
-                       aVideoTaskQueue,
+                       aTaskQueue,
                        aCallback);
   }
 
   return m.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
 AgnosticDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig,
-                                          FlushableTaskQueue* aAudioTaskQueue,
+                                          TaskQueue* aTaskQueue,
                                           MediaDataDecoderCallback* aCallback,
                                           DecoderDoctorDiagnostics* aDiagnostics)
 {
   RefPtr<MediaDataDecoder> m;
 
   if (VorbisDataDecoder::IsVorbis(aConfig.mMimeType)) {
     m = new VorbisDataDecoder(*aConfig.GetAsAudioInfo(),
-                              aAudioTaskQueue,
+                              aTaskQueue,
                               aCallback);
   } else if (OpusDataDecoder::IsOpus(aConfig.mMimeType)) {
     m = new OpusDataDecoder(*aConfig.GetAsAudioInfo(),
-                            aAudioTaskQueue,
+                            aTaskQueue,
                             aCallback);
   } else if (WaveDataDecoder::IsWave(aConfig.mMimeType)) {
     m = new WaveDataDecoder(*aConfig.GetAsAudioInfo(), aCallback);
   }
 
   return m.forget();
 }
 
--- a/dom/media/platforms/agnostic/AgnosticDecoderModule.h
+++ b/dom/media/platforms/agnostic/AgnosticDecoderModule.h
@@ -20,23 +20,23 @@ public:
   }
 
 protected:
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const VideoInfo& aConfig,
                      layers::LayersBackend aLayersBackend,
                      layers::ImageContainer* aImageContainer,
-                     FlushableTaskQueue* aVideoTaskQueue,
+                     TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) override;
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
   CreateAudioDecoder(const AudioInfo& aConfig,
-                     FlushableTaskQueue* aAudioTaskQueue,
+                     TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) override;
 };
 
 } // namespace mozilla
 
 #endif /* AgnosticDecoderModule_h_ */
--- a/dom/media/platforms/agnostic/BlankDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/BlankDecoderModule.cpp
@@ -196,32 +196,32 @@ private:
 class BlankDecoderModule : public PlatformDecoderModule {
 public:
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const VideoInfo& aConfig,
                      layers::LayersBackend aLayersBackend,
                      layers::ImageContainer* aImageContainer,
-                     FlushableTaskQueue* aVideoTaskQueue,
+                     TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) override {
     BlankVideoDataCreator* creator = new BlankVideoDataCreator(
       aConfig.mDisplay.width, aConfig.mDisplay.height, aImageContainer);
     RefPtr<MediaDataDecoder> decoder =
       new BlankMediaDataDecoder<BlankVideoDataCreator>(creator,
                                                        aCallback,
                                                        TrackInfo::kVideoTrack);
     return decoder.forget();
   }
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
   CreateAudioDecoder(const AudioInfo& aConfig,
-                     FlushableTaskQueue* aAudioTaskQueue,
+                     TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) override {
     BlankAudioDataCreator* creator = new BlankAudioDataCreator(
       aConfig.mChannels, aConfig.mRate);
 
     RefPtr<MediaDataDecoder> decoder =
       new BlankMediaDataDecoder<BlankAudioDataCreator>(creator,
                                                        aCallback,
--- a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
@@ -233,38 +233,38 @@ CreateDecoderWrapper(MediaDataDecoderCal
     new EMEMediaDataDecoderProxy(thread.forget(), aCallback, aProxy, aTaskQueue));
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
 EMEDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig,
                                      layers::LayersBackend aLayersBackend,
                                      layers::ImageContainer* aImageContainer,
-                                     FlushableTaskQueue* aVideoTaskQueue,
+                                     TaskQueue* aTaskQueue,
                                      MediaDataDecoderCallback* aCallback,
                                      DecoderDoctorDiagnostics* aDiagnostics)
 {
   MOZ_ASSERT(aConfig.mCrypto.mValid);
 
   if (SupportsMimeType(aConfig.mMimeType, nullptr)) {
     // GMP decodes. Assume that means it can decrypt too.
-    RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback, mProxy, aVideoTaskQueue);
+    RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback, mProxy, aTaskQueue);
     wrapper->SetProxyTarget(new EMEVideoDecoder(mProxy,
                                                 aConfig,
                                                 aLayersBackend,
                                                 aImageContainer,
-                                                aVideoTaskQueue,
+                                                aTaskQueue,
                                                 wrapper->Callback()));
     return wrapper.forget();
   }
 
   MOZ_ASSERT(mPDM);
   RefPtr<MediaDataDecoder> decoder(
     mPDM->CreateDecoder(aConfig,
-                        aVideoTaskQueue,
+                        aTaskQueue,
                         aCallback,
                         aDiagnostics,
                         aLayersBackend,
                         aImageContainer));
   if (!decoder) {
     return nullptr;
   }
 
@@ -272,35 +272,35 @@ EMEDecoderModule::CreateVideoDecoder(con
                                                          aCallback,
                                                          mProxy,
                                                          AbstractThread::GetCurrent()->AsTaskQueue()));
   return emeDecoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
 EMEDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig,
-                                     FlushableTaskQueue* aAudioTaskQueue,
+                                     TaskQueue* aTaskQueue,
                                      MediaDataDecoderCallback* aCallback,
                                      DecoderDoctorDiagnostics* aDiagnostics)
 {
   MOZ_ASSERT(aConfig.mCrypto.mValid);
 
   if (SupportsMimeType(aConfig.mMimeType, nullptr)) {
     // GMP decodes. Assume that means it can decrypt too.
-    RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback, mProxy, aAudioTaskQueue);
+    RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback, mProxy, aTaskQueue);
     wrapper->SetProxyTarget(new EMEAudioDecoder(mProxy,
                                                 aConfig,
-                                                aAudioTaskQueue,
+                                                aTaskQueue,
                                                 wrapper->Callback()));
     return wrapper.forget();
   }
 
   MOZ_ASSERT(mPDM);
   RefPtr<MediaDataDecoder> decoder(
-    mPDM->CreateDecoder(aConfig, aAudioTaskQueue, aCallback, aDiagnostics));
+    mPDM->CreateDecoder(aConfig, aTaskQueue, aCallback, aDiagnostics));
   if (!decoder) {
     return nullptr;
   }
 
   RefPtr<MediaDataDecoder> emeDecoder(new EMEDecryptor(decoder,
                                                          aCallback,
                                                          mProxy,
                                                          AbstractThread::GetCurrent()->AsTaskQueue()));
--- a/dom/media/platforms/agnostic/eme/EMEDecoderModule.h
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.h
@@ -24,24 +24,24 @@ public:
   virtual ~EMEDecoderModule();
 
 protected:
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const VideoInfo& aConfig,
                     layers::LayersBackend aLayersBackend,
                     layers::ImageContainer* aImageContainer,
-                    FlushableTaskQueue* aVideoTaskQueue,
+                    TaskQueue* aTaskQueue,
                     MediaDataDecoderCallback* aCallback,
                     DecoderDoctorDiagnostics* aDiagnostics) override;
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
   CreateAudioDecoder(const AudioInfo& aConfig,
-                     FlushableTaskQueue* aAudioTaskQueue,
+                     TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) override;
 
   ConversionRequired
   DecoderNeedsConversion(const TrackInfo& aConfig) const override;
 
   bool
   SupportsMimeType(const nsACString& aMimeType,
--- a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp
@@ -44,17 +44,17 @@ CreateDecoderWrapper(MediaDataDecoderCal
   RefPtr<MediaDataDecoderProxy> decoder(new MediaDataDecoderProxy(thread.forget(), aCallback));
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
 GMPDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig,
                                      layers::LayersBackend aLayersBackend,
                                      layers::ImageContainer* aImageContainer,
-                                     FlushableTaskQueue* aVideoTaskQueue,
+                                     TaskQueue* aTaskQueue,
                                      MediaDataDecoderCallback* aCallback,
                                      DecoderDoctorDiagnostics* aDiagnostics)
 {
   if (!aConfig.mMimeType.EqualsLiteral("video/avc")) {
     return nullptr;
   }
 
   if (aDiagnostics) {
@@ -63,41 +63,41 @@ GMPDecoderModule::CreateVideoDecoder(con
       aDiagnostics->SetGMP(preferredGMP.value());
     }
   }
 
   RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback);
   wrapper->SetProxyTarget(new GMPVideoDecoder(aConfig,
                                               aLayersBackend,
                                               aImageContainer,
-                                              aVideoTaskQueue,
+                                              aTaskQueue,
                                               wrapper->Callback()));
   return wrapper.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
 GMPDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig,
-                                     FlushableTaskQueue* aAudioTaskQueue,
+                                     TaskQueue* aTaskQueue,
                                      MediaDataDecoderCallback* aCallback,
                                      DecoderDoctorDiagnostics* aDiagnostics)
 {
   if (!aConfig.mMimeType.EqualsLiteral("audio/mp4a-latm")) {
     return nullptr;
   }
 
   if (aDiagnostics) {
     const Maybe<nsCString> preferredGMP = PreferredGMP(aConfig.mMimeType);
     if (preferredGMP.isSome()) {
       aDiagnostics->SetGMP(preferredGMP.value());
     }
   }
 
   RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback);
   wrapper->SetProxyTarget(new GMPAudioDecoder(aConfig,
-                                              aAudioTaskQueue,
+                                              aTaskQueue,
                                               wrapper->Callback()));
   return wrapper.forget();
 }
 
 PlatformDecoderModule::ConversionRequired
 GMPDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const
 {
   // GMPVideoCodecType::kGMPVideoCodecH264 specifies that encoded frames must be in AVCC format.
--- a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h
+++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h
@@ -31,24 +31,24 @@ public:
 
   virtual ~GMPDecoderModule();
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const VideoInfo& aConfig,
                      layers::LayersBackend aLayersBackend,
                      layers::ImageContainer* aImageContainer,
-                     FlushableTaskQueue* aVideoTaskQueue,
+                     TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) override;
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
   CreateAudioDecoder(const AudioInfo& aConfig,
-                     FlushableTaskQueue* aAudioTaskQueue,
+                     TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) override;
 
   ConversionRequired
   DecoderNeedsConversion(const TrackInfo& aConfig) const override;
 
   bool
   SupportsMimeType(const nsACString& aMimeType,
--- a/dom/media/platforms/android/AndroidDecoderModule.cpp
+++ b/dom/media/platforms/android/AndroidDecoderModule.cpp
@@ -264,33 +264,33 @@ AndroidDecoderModule::SupportsMimeType(c
   // Accessing a stale local reference leading to a SIGSEGV crash.
   // To avoid this we check for wav types here.
   if (aMimeType.EqualsLiteral("audio/x-wav") ||
       aMimeType.EqualsLiteral("audio/wave; codecs=1") ||
       aMimeType.EqualsLiteral("audio/wave; codecs=6") ||
       aMimeType.EqualsLiteral("audio/wave; codecs=7") ||
       aMimeType.EqualsLiteral("audio/wave; codecs=65534")) {
     return false;
-  }  
+  }
 
   if ((VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP8) &&
        !GetFeatureStatus(nsIGfxInfo::FEATURE_VP8_HW_DECODE)) ||
       (VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP9) &&
        !GetFeatureStatus(nsIGfxInfo::FEATURE_VP9_HW_DECODE))) {
     return false;
   }
 
   return widget::HardwareCodecCapabilityUtils::FindDecoderCodecInfoForMimeType(
       nsCString(TranslateMimeType(aMimeType)));
 }
 
 already_AddRefed<MediaDataDecoder>
 AndroidDecoderModule::CreateVideoDecoder(
     const VideoInfo& aConfig, layers::LayersBackend aLayersBackend,
-    layers::ImageContainer* aImageContainer, FlushableTaskQueue* aVideoTaskQueue,
+    layers::ImageContainer* aImageContainer, TaskQueue* aTaskQueue,
     MediaDataDecoderCallback* aCallback,
     DecoderDoctorDiagnostics* aDiagnostics)
 {
   MediaFormat::LocalRef format;
 
   NS_ENSURE_SUCCESS(MediaFormat::CreateVideoFormat(
       TranslateMimeType(aConfig.mMimeType),
       aConfig.mDisplay.width,
@@ -300,17 +300,17 @@ AndroidDecoderModule::CreateVideoDecoder
   RefPtr<MediaDataDecoder> decoder =
     new VideoDataDecoder(aConfig, format, aCallback, aImageContainer);
 
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
 AndroidDecoderModule::CreateAudioDecoder(
-    const AudioInfo& aConfig, FlushableTaskQueue* aAudioTaskQueue,
+    const AudioInfo& aConfig, TaskQueue* aTaskQueue,
     MediaDataDecoderCallback* aCallback,
     DecoderDoctorDiagnostics* aDiagnostics)
 {
   MOZ_ASSERT(aConfig.mBitDepth == 16, "We only handle 16-bit audio!");
 
   MediaFormat::LocalRef format;
 
   LOG("CreateAudioFormat with mimeType=%s, mRate=%d, channels=%d",
--- a/dom/media/platforms/android/AndroidDecoderModule.h
+++ b/dom/media/platforms/android/AndroidDecoderModule.h
@@ -20,23 +20,23 @@ namespace mozilla {
 typedef std::deque<RefPtr<MediaRawData>> SampleQueue;
 
 class AndroidDecoderModule : public PlatformDecoderModule {
 public:
   already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const VideoInfo& aConfig,
                      layers::LayersBackend aLayersBackend,
                      layers::ImageContainer* aImageContainer,
-                     FlushableTaskQueue* aVideoTaskQueue,
+                     TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) override;
 
   already_AddRefed<MediaDataDecoder>
   CreateAudioDecoder(const AudioInfo& aConfig,
-                     FlushableTaskQueue* aAudioTaskQueue,
+                     TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) override;
 
 
   AndroidDecoderModule() {}
   virtual ~AndroidDecoderModule() {}
 
   bool SupportsMimeType(const nsACString& aMimeType,
--- a/dom/media/platforms/apple/AppleDecoderModule.cpp
+++ b/dom/media/platforms/apple/AppleDecoderModule.cpp
@@ -72,49 +72,49 @@ AppleDecoderModule::Startup()
   }
   return NS_OK;
 }
 
 already_AddRefed<MediaDataDecoder>
 AppleDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig,
                                        layers::LayersBackend aLayersBackend,
                                        layers::ImageContainer* aImageContainer,
-                                       FlushableTaskQueue* aVideoTaskQueue,
+                                       TaskQueue* aTaskQueue,
                                        MediaDataDecoderCallback* aCallback,
                                        DecoderDoctorDiagnostics* aDiagnostics)
 {
   RefPtr<MediaDataDecoder> decoder;
 
   if (sIsVDAAvailable && (!sIsVTHWAvailable || MediaPrefs::AppleForceVDA())) {
     decoder =
       AppleVDADecoder::CreateVDADecoder(aConfig,
-                                        aVideoTaskQueue,
+                                        aTaskQueue,
                                         aCallback,
                                         aImageContainer);
     if (decoder) {
       return decoder.forget();
     }
   }
   // We fallback here if VDA isn't available, or is available but isn't
   // supported by the current platform.
   if (sIsVTAvailable) {
     decoder =
-      new AppleVTDecoder(aConfig, aVideoTaskQueue, aCallback, aImageContainer);
+      new AppleVTDecoder(aConfig, aTaskQueue, aCallback, aImageContainer);
   }
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
 AppleDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig,
-                                       FlushableTaskQueue* aAudioTaskQueue,
+                                       TaskQueue* aTaskQueue,
                                        MediaDataDecoderCallback* aCallback,
                                        DecoderDoctorDiagnostics* aDiagnostics)
 {
   RefPtr<MediaDataDecoder> decoder =
-    new AppleATDecoder(aConfig, aAudioTaskQueue, aCallback);
+    new AppleATDecoder(aConfig, aTaskQueue, aCallback);
   return decoder.forget();
 }
 
 bool
 AppleDecoderModule::SupportsMimeType(const nsACString& aMimeType,
                                      DecoderDoctorDiagnostics* aDiagnostics) const
 {
   return (sIsCoreMediaAvailable &&
--- a/dom/media/platforms/apple/AppleDecoderModule.h
+++ b/dom/media/platforms/apple/AppleDecoderModule.h
@@ -18,24 +18,24 @@ public:
 
   nsresult Startup() override;
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const VideoInfo& aConfig,
                      layers::LayersBackend aLayersBackend,
                      layers::ImageContainer* aImageContainer,
-                     FlushableTaskQueue* aVideoTaskQueue,
+                     TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) override;
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
   CreateAudioDecoder(const AudioInfo& aConfig,
-                     FlushableTaskQueue* aAudioTaskQueue,
+                     TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) override;
 
   bool SupportsMimeType(const nsACString& aMimeType,
                         DecoderDoctorDiagnostics* aDiagnostics) const override;
 
   ConversionRequired
   DecoderNeedsConversion(const TrackInfo& aConfig) const override;
--- a/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
@@ -10,17 +10,17 @@
 #include "TimeUnits.h"
 
 #define MAX_CHANNELS 16
 
 namespace mozilla
 {
 
 FFmpegAudioDecoder<LIBAV_VER>::FFmpegAudioDecoder(FFmpegLibWrapper* aLib,
-  FlushableTaskQueue* aTaskQueue, MediaDataDecoderCallback* aCallback,
+  TaskQueue* aTaskQueue, MediaDataDecoderCallback* aCallback,
   const AudioInfo& aConfig)
   : FFmpegDataDecoder(aLib, aTaskQueue, aCallback, GetCodecId(aConfig.mMimeType))
 {
   MOZ_COUNT_CTOR(FFmpegAudioDecoder);
   // Use a new MediaByteBuffer as the object will be modified during initialization.
   mExtraData = new MediaByteBuffer;
   mExtraData->AppendElements(*aConfig.mCodecSpecificConfig);
 }
--- a/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.h
+++ b/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.h
@@ -16,17 +16,17 @@ namespace mozilla
 template <int V> class FFmpegAudioDecoder
 {
 };
 
 template <>
 class FFmpegAudioDecoder<LIBAV_VER> : public FFmpegDataDecoder<LIBAV_VER>
 {
 public:
-  FFmpegAudioDecoder(FFmpegLibWrapper* aLib, FlushableTaskQueue* aTaskQueue,
+  FFmpegAudioDecoder(FFmpegLibWrapper* aLib, TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      const AudioInfo& aConfig);
   virtual ~FFmpegAudioDecoder();
 
   RefPtr<InitPromise> Init() override;
   void InitCodecContext() override;
   static AVCodecID GetCodecId(const nsACString& aMimeType);
   const char* GetDescriptionName() const override
--- a/dom/media/platforms/ffmpeg/FFmpegDecoderModule.h
+++ b/dom/media/platforms/ffmpeg/FFmpegDecoderModule.h
@@ -29,37 +29,37 @@ public:
 
   explicit FFmpegDecoderModule(FFmpegLibWrapper* aLib) : mLib(aLib) {}
   virtual ~FFmpegDecoderModule() {}
 
   already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const VideoInfo& aConfig,
                      layers::LayersBackend aLayersBackend,
                      layers::ImageContainer* aImageContainer,
-                     FlushableTaskQueue* aVideoTaskQueue,
+                     TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) override
   {
     RefPtr<MediaDataDecoder> decoder =
-      new FFmpegVideoDecoder<V>(mLib, aVideoTaskQueue, aCallback, aConfig,
+      new FFmpegVideoDecoder<V>(mLib, aTaskQueue, aCallback, aConfig,
                                 aImageContainer);
     return decoder.forget();
   }
 
   already_AddRefed<MediaDataDecoder>
   CreateAudioDecoder(const AudioInfo& aConfig,
-                     FlushableTaskQueue* aAudioTaskQueue,
+                     TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) override
   {
 #ifdef USING_MOZFFVPX
     return nullptr;
 #else
     RefPtr<MediaDataDecoder> decoder =
-      new FFmpegAudioDecoder<V>(mLib, aAudioTaskQueue, aCallback, aConfig);
+      new FFmpegAudioDecoder<V>(mLib, aTaskQueue, aCallback, aConfig);
     return decoder.forget();
 #endif
   }
 
   bool SupportsMimeType(const nsACString& aMimeType,
                         DecoderDoctorDiagnostics* aDiagnostics) const override
   {
     AVCodecID videoCodec = FFmpegVideoDecoder<V>::GetCodecId(aMimeType);
--- a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
@@ -96,17 +96,17 @@ FFmpegVideoDecoder<LIBAV_VER>::PtsCorrec
 {
   mNumFaultyPts = 0;
   mNumFaultyDts = 0;
   mLastPts = INT64_MIN;
   mLastDts = INT64_MIN;
 }
 
 FFmpegVideoDecoder<LIBAV_VER>::FFmpegVideoDecoder(FFmpegLibWrapper* aLib,
-  FlushableTaskQueue* aTaskQueue, MediaDataDecoderCallback* aCallback,
+  TaskQueue* aTaskQueue, MediaDataDecoderCallback* aCallback,
   const VideoInfo& aConfig,
   ImageContainer* aImageContainer)
   : FFmpegDataDecoder(aLib, aTaskQueue, aCallback, GetCodecId(aConfig.mMimeType))
   , mImageContainer(aImageContainer)
   , mInfo(aConfig)
   , mCodecParser(nullptr)
   , mLastInputDts(INT64_MIN)
 {
--- a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h
+++ b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h
@@ -22,17 +22,17 @@ class FFmpegVideoDecoder : public FFmpeg
 
 template <>
 class FFmpegVideoDecoder<LIBAV_VER> : public FFmpegDataDecoder<LIBAV_VER>
 {
   typedef mozilla::layers::Image Image;
   typedef mozilla::layers::ImageContainer ImageContainer;
 
 public:
-  FFmpegVideoDecoder(FFmpegLibWrapper* aLib, FlushableTaskQueue* aTaskQueue,
+  FFmpegVideoDecoder(FFmpegLibWrapper* aLib, TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      const VideoInfo& aConfig,
                      ImageContainer* aImageContainer);
   virtual ~FFmpegVideoDecoder();
 
   RefPtr<InitPromise> Init() override;
   void InitCodecContext() override;
   const char* GetDescriptionName() const override
--- a/dom/media/platforms/gonk/GonkDecoderModule.cpp
+++ b/dom/media/platforms/gonk/GonkDecoderModule.cpp
@@ -17,29 +17,29 @@ GonkDecoderModule::GonkDecoderModule()
 GonkDecoderModule::~GonkDecoderModule()
 {
 }
 
 already_AddRefed<MediaDataDecoder>
 GonkDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig,
                                      mozilla::layers::LayersBackend aLayersBackend,
                                      mozilla::layers::ImageContainer* aImageContainer,
-                                     FlushableTaskQueue* aVideoTaskQueue,
+                                     TaskQueue* aTaskQueue,
                                      MediaDataDecoderCallback* aCallback,
                                      DecoderDoctorDiagnostics* aDiagnostics)
 {
   RefPtr<MediaDataDecoder> decoder =
   new GonkMediaDataDecoder(new GonkVideoDecoderManager(aImageContainer, aConfig),
                            aCallback);
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
 GonkDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig,
-                                      FlushableTaskQueue* aAudioTaskQueue,
+                                      TaskQueue* aTaskQueue,
                                       MediaDataDecoderCallback* aCallback,
                                       DecoderDoctorDiagnostics* aDiagnostics)
 {
   RefPtr<MediaDataDecoder> decoder =
   new GonkMediaDataDecoder(new GonkAudioDecoderManager(aConfig),
                            aCallback);
   return decoder.forget();
 }
--- a/dom/media/platforms/gonk/GonkDecoderModule.h
+++ b/dom/media/platforms/gonk/GonkDecoderModule.h
@@ -16,24 +16,24 @@ public:
   GonkDecoderModule();
   virtual ~GonkDecoderModule();
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const VideoInfo& aConfig,
                      mozilla::layers::LayersBackend aLayersBackend,
                      mozilla::layers::ImageContainer* aImageContainer,
-                     FlushableTaskQueue* aVideoTaskQueue,
+                     TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) override;
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
   CreateAudioDecoder(const AudioInfo& aConfig,
-                     FlushableTaskQueue* aAudioTaskQueue,
+                     TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) override;
 
   ConversionRequired
   DecoderNeedsConversion(const TrackInfo& aConfig) const override;
 
   bool SupportsMimeType(const nsACString& aMimeType,
                         DecoderDoctorDiagnostics* aDiagnostics) const override;
--- a/dom/media/platforms/omx/OmxDecoderModule.cpp
+++ b/dom/media/platforms/omx/OmxDecoderModule.cpp
@@ -10,27 +10,27 @@
 #include "OmxPlatformLayer.h"
 
 namespace mozilla {
 
 already_AddRefed<MediaDataDecoder>
 OmxDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig,
                                      mozilla::layers::LayersBackend aLayersBackend,
                                      mozilla::layers::ImageContainer* aImageContainer,
-                                     FlushableTaskQueue* aVideoTaskQueue,
+                                     TaskQueue* aTaskQueue,
                                      MediaDataDecoderCallback* aCallback,
                                      DecoderDoctorDiagnostics* aDiagnostics)
 {
   RefPtr<OmxDataDecoder> decoder = new OmxDataDecoder(aConfig, aCallback, aImageContainer);
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
 OmxDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig,
-                                     FlushableTaskQueue* aAudioTaskQueue,
+                                     TaskQueue* aTaskQueue,
                                      MediaDataDecoderCallback* aCallback,
                                      DecoderDoctorDiagnostics* aDiagnostics)
 {
   RefPtr<OmxDataDecoder> decoder = new OmxDataDecoder(aConfig, aCallback, nullptr);
   return decoder.forget();
 }
 
 PlatformDecoderModule::ConversionRequired
--- a/dom/media/platforms/omx/OmxDecoderModule.h
+++ b/dom/media/platforms/omx/OmxDecoderModule.h
@@ -12,23 +12,23 @@
 namespace mozilla {
 
 class OmxDecoderModule : public PlatformDecoderModule {
 public:
   already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const VideoInfo& aConfig,
                      mozilla::layers::LayersBackend aLayersBackend,
                      mozilla::layers::ImageContainer* aImageContainer,
-                     FlushableTaskQueue* aVideoTaskQueue,
+                     TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) override;
 
   already_AddRefed<MediaDataDecoder>
   CreateAudioDecoder(const AudioInfo& aConfig,
-                     FlushableTaskQueue* aAudioTaskQueue,
+                     TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) override;
 
   bool SupportsMimeType(const nsACString& aMimeType,
                         DecoderDoctorDiagnostics* aDiagnostics) const override;
 
   ConversionRequired DecoderNeedsConversion(const TrackInfo& aConfig) const override;
 };
--- a/dom/media/platforms/wmf/WMFDecoderModule.cpp
+++ b/dom/media/platforms/wmf/WMFDecoderModule.cpp
@@ -73,50 +73,50 @@ WMFDecoderModule::Startup()
   mWMFInitialized = SUCCEEDED(wmf::MFStartup());
   return mWMFInitialized ? NS_OK : NS_ERROR_FAILURE;
 }
 
 already_AddRefed<MediaDataDecoder>
 WMFDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig,
                                      layers::LayersBackend aLayersBackend,
                                      layers::ImageContainer* aImageContainer,
-                                     FlushableTaskQueue* aVideoTaskQueue,
+                                     TaskQueue* aTaskQueue,
                                      MediaDataDecoderCallback* aCallback,
                                      DecoderDoctorDiagnostics* aDiagnostics)
 {
   nsAutoPtr<WMFVideoMFTManager> manager(
     new WMFVideoMFTManager(aConfig,
                            aLayersBackend,
                            aImageContainer,
                            sDXVAEnabled));
 
   if (!manager->Init()) {
     return nullptr;
   }
 
   RefPtr<MediaDataDecoder> decoder =
-    new WMFMediaDataDecoder(manager.forget(), aVideoTaskQueue, aCallback);
+    new WMFMediaDataDecoder(manager.forget(), aTaskQueue, aCallback);
 
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
 WMFDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig,
-                                     FlushableTaskQueue* aAudioTaskQueue,
+                                     TaskQueue* aTaskQueue,
                                      MediaDataDecoderCallback* aCallback,
                                      DecoderDoctorDiagnostics* aDiagnostics)
 {
   nsAutoPtr<WMFAudioMFTManager> manager(new WMFAudioMFTManager(aConfig));
 
   if (!manager->Init()) {
     return nullptr;
   }
 
   RefPtr<MediaDataDecoder> decoder =
-    new WMFMediaDataDecoder(manager.forget(), aAudioTaskQueue, aCallback);
+    new WMFMediaDataDecoder(manager.forget(), aTaskQueue, aCallback);
   return decoder.forget();
 }
 
 static bool
 CanCreateMFTDecoder(const GUID& aGuid)
 {
   if (FAILED(wmf::MFStartup())) {
     return false;
--- a/dom/media/platforms/wmf/WMFDecoderModule.h
+++ b/dom/media/platforms/wmf/WMFDecoderModule.h
@@ -18,23 +18,23 @@ public:
 
   // Initializes the module, loads required dynamic libraries, etc.
   nsresult Startup() override;
 
   already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const VideoInfo& aConfig,
                      layers::LayersBackend aLayersBackend,
                      layers::ImageContainer* aImageContainer,
-                     FlushableTaskQueue* aVideoTaskQueue,
+                     TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) override;
 
   already_AddRefed<MediaDataDecoder>
   CreateAudioDecoder(const AudioInfo& aConfig,
-                     FlushableTaskQueue* aAudioTaskQueue,
+                     TaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) override;
 
   bool SupportsMimeType(const nsACString& aMimeType,
                         DecoderDoctorDiagnostics* aDiagnostics) const override;
 
   ConversionRequired
   DecoderNeedsConversion(const TrackInfo& aConfig) const override;
--- a/dom/media/platforms/wrappers/H264Converter.cpp
+++ b/dom/media/platforms/wrappers/H264Converter.cpp
@@ -14,25 +14,25 @@
 
 namespace mozilla
 {
 
 H264Converter::H264Converter(PlatformDecoderModule* aPDM,
                              const VideoInfo& aConfig,
                              layers::LayersBackend aLayersBackend,
                              layers::ImageContainer* aImageContainer,
-                             FlushableTaskQueue* aVideoTaskQueue,
+                             TaskQueue* aTaskQueue,
                              MediaDataDecoderCallback* aCallback,
                              DecoderDoctorDiagnostics* aDiagnostics)
   : mPDM(aPDM)
   , mOriginalConfig(aConfig)
   , mCurrentConfig(aConfig)
   , mLayersBackend(aLayersBackend)
   , mImageContainer(aImageContainer)
-  , mVideoTaskQueue(aVideoTaskQueue)
+  , mTaskQueue(aTaskQueue)
   , mCallback(aCallback)
   , mDecoder(nullptr)
   , mNeedAVCC(aPDM->DecoderNeedsConversion(aConfig) == PlatformDecoderModule::kNeedAVCC)
   , mLastError(NS_OK)
 {
   CreateDecoder(aDiagnostics);
 }
 
@@ -144,17 +144,17 @@ H264Converter::CreateDecoder(DecoderDoct
     // When using a decoder handling AnnexB, we get here only once from the
     // constructor. We do want to get the dimensions extracted from the SPS.
     mOriginalConfig = mCurrentConfig;
   }
 
   mDecoder = mPDM->CreateVideoDecoder(mNeedAVCC ? mCurrentConfig : mOriginalConfig,
                                       mLayersBackend,
                                       mImageContainer,
-                                      mVideoTaskQueue,
+                                      mTaskQueue,
                                       mCallback,
                                       aDiagnostics);
   if (!mDecoder) {
     mLastError = NS_ERROR_FAILURE;
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
--- a/dom/media/platforms/wrappers/H264Converter.h
+++ b/dom/media/platforms/wrappers/H264Converter.h
@@ -20,17 +20,17 @@ namespace mozilla {
 
 class H264Converter : public MediaDataDecoder {
 public:
 
   H264Converter(PlatformDecoderModule* aPDM,
                 const VideoInfo& aConfig,
                 layers::LayersBackend aLayersBackend,
                 layers::ImageContainer* aImageContainer,
-                FlushableTaskQueue* aVideoTaskQueue,
+                TaskQueue* aTaskQueue,
                 MediaDataDecoderCallback* aCallback,
                 DecoderDoctorDiagnostics* aDiagnostics);
   virtual ~H264Converter();
 
   RefPtr<InitPromise> Init() override;
   nsresult Input(MediaRawData* aSample) override;
   nsresult Flush() override;
   nsresult Drain() override;
@@ -60,17 +60,17 @@ private:
   void OnDecoderInitDone(const TrackType aTrackType);
   void OnDecoderInitFailed(MediaDataDecoder::DecoderFailureReason aReason);
 
   RefPtr<PlatformDecoderModule> mPDM;
   VideoInfo mOriginalConfig;
   VideoInfo mCurrentConfig;
   layers::LayersBackend mLayersBackend;
   RefPtr<layers::ImageContainer> mImageContainer;
-  RefPtr<FlushableTaskQueue> mVideoTaskQueue;
+  const RefPtr<TaskQueue> mTaskQueue;
   nsTArray<RefPtr<MediaRawData>> mMediaRawSamples;
   MediaDataDecoderCallback* mCallback;
   RefPtr<MediaDataDecoder> mDecoder;
   MozPromiseRequestHolder<InitPromise> mInitPromiseRequest;
   bool mNeedAVCC;
   nsresult mLastError;
 };
 
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -124,16 +124,17 @@ skip-if = buildapp == 'b2g' || buildapp 
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_bug834153.html]
 [test_peerConnection_bug1013809.html]
 skip-if = toolkit == 'gonk' # B2G emulator is too slow to handle a two-way audio call reliably
 [test_peerConnection_bug1042791.html]
 skip-if = buildapp == 'b2g' || buildapp == 'mulet' || os == 'android' # bug 1043403 # Bug 1141029 Mulet parity with B2G Desktop for TC
 [test_peerConnection_bug1064223.html]
 [test_peerConnection_capturedVideo.html]
+subsuite = clipboard
 tags=capturestream
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18' # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_captureStream_canvas_2d.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_multiple_captureStream_canvas_2d.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_captureStream_canvas_webgl.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
--- a/dom/presentation/PresentationReceiver.cpp
+++ b/dom/presentation/PresentationReceiver.cpp
@@ -62,33 +62,18 @@ PresentationReceiver::Init()
   mWindowId = GetOwner()->WindowID();
 
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if (NS_WARN_IF(!service)) {
     return false;
   }
 
-  // A session may already be connecting before the web content
-  // request for access in a receiving browsing context.
-  nsAutoString sessionId;
-  nsresult rv = service->GetExistentSessionIdAtLaunch(mWindowId, sessionId);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return false;
-  }
-
-  if (!sessionId.IsEmpty()) {
-    rv = NotifySessionConnect(mWindowId, sessionId);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return false;
-    }
-  }
-
   // Register listener for incoming sessions.
-  rv = service->RegisterRespondingListener(mWindowId, this);
+  nsresult rv = service->RegisterRespondingListener(mWindowId, this);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
 
   return true;
 }
 
 void PresentationReceiver::Shutdown()
--- a/dom/presentation/PresentationService.cpp
+++ b/dom/presentation/PresentationService.cpp
@@ -143,17 +143,20 @@ PresentationDeviceRequest::Cancel()
 
   return info->ReplyError(NS_ERROR_DOM_ABORT_ERR);
 }
 
 /*
  * Implementation of PresentationService
  */
 
-NS_IMPL_ISUPPORTS(PresentationService, nsIPresentationService, nsIObserver)
+NS_IMPL_ISUPPORTS_INHERITED(PresentationService,
+                            PresentationServiceBase,
+                            nsIPresentationService,
+                            nsIObserver)
 
 PresentationService::PresentationService()
   : mIsAvailable(false)
 {
 }
 
 PresentationService::~PresentationService()
 {
@@ -220,21 +223,21 @@ PresentationService::Observe(nsISupports
   return NS_ERROR_UNEXPECTED;
 }
 
 void
 PresentationService::HandleShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  Shutdown();
+
   mAvailabilityListeners.Clear();
-  mRespondingListeners.Clear();
   mSessionInfoAtController.Clear();
   mSessionInfoAtReceiver.Clear();
-  mRespondingSessionIds.Clear();
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   if (obs) {
     obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
     obs->RemoveObserver(this, PRESENTATION_DEVICE_CHANGE_TOPIC);
     obs->RemoveObserver(this, PRESENTATION_SESSION_REQUEST_TOPIC);
   }
 }
@@ -401,22 +404,17 @@ PresentationService::StartSession(const 
   MOZ_ASSERT(!aSessionId.IsEmpty());
 
   // Create session info  and set the callback. The callback is called when the
   // request is finished.
   RefPtr<PresentationSessionInfo> info =
     new PresentationControllingInfo(aUrl, aSessionId, aCallback);
   mSessionInfoAtController.Put(aSessionId, info);
 
-  // Only track the info when an actual window ID, which would never be 0, is
-  // provided (for an in-process sender page).
-  if (aWindowId != 0) {
-    mRespondingSessionIds.Put(aWindowId, new nsString(aSessionId));
-    mRespondingWindowIds.Put(aSessionId, aWindowId);
-  }
+  AddRespondingSessionId(aWindowId, aSessionId);
 
   nsCOMPtr<nsIPresentationDeviceRequest> request =
     new PresentationDeviceRequest(aUrl, aSessionId, aOrigin);
 
   if (aDeviceId.IsVoid()) {
     // Pop up a prompt and ask user to select a device.
     nsCOMPtr<nsIPresentationDevicePrompt> prompt =
       do_GetService(PRESENTATION_DEVICE_PROMPT_CONTRACTID);
@@ -586,27 +584,37 @@ PresentationService::UnregisterSessionLi
     NS_WARN_IF(NS_FAILED(info->Close(NS_OK, nsIPresentationSessionListener::STATE_TERMINATED)));
     UntrackSessionInfo(aSessionId, aRole);
     return info->SetListener(nullptr);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
-PresentationService::RegisterRespondingListener(uint64_t aWindowId,
-                                                nsIPresentationRespondingListener* aListener)
+PresentationService::RegisterRespondingListener(
+  uint64_t aWindowId,
+  nsIPresentationRespondingListener* aListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aListener);
 
   nsCOMPtr<nsIPresentationRespondingListener> listener;
   if (mRespondingListeners.Get(aWindowId, getter_AddRefs(listener))) {
     return (listener == aListener) ? NS_OK : NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
+  nsTArray<nsString>* sessionIdArray;
+  if (!mRespondingSessionIds.Get(aWindowId, &sessionIdArray)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  for (const auto& id : *sessionIdArray) {
+    aListener->NotifySessionConnect(aWindowId, id);
+  }
+
   mRespondingListeners.Put(aWindowId, aListener);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationService::UnregisterRespondingListener(uint64_t aWindowId)
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -614,42 +622,37 @@ PresentationService::UnregisterRespondin
   mRespondingListeners.Remove(aWindowId);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationService::GetExistentSessionIdAtLaunch(uint64_t aWindowId,
                                                   nsAString& aSessionId)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsString* sessionId = mRespondingSessionIds.Get(aWindowId);
-  if (sessionId) {
-    aSessionId.Assign(*sessionId);
-  } else {
-    aSessionId.Truncate();
-  }
-  return NS_OK;
+  return GetExistentSessionIdAtLaunchInternal(aWindowId, aSessionId);
 }
 
 NS_IMETHODIMP
 PresentationService::NotifyReceiverReady(const nsAString& aSessionId,
                                          uint64_t aWindowId)
 {
   RefPtr<PresentationSessionInfo> info =
     GetSessionInfo(aSessionId, nsIPresentationService::ROLE_RECEIVER);
   if (NS_WARN_IF(!info)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  // Only track the responding info when an actual window ID, which would never
-  // be 0, is provided (for an in-process receiver page).
-  if (aWindowId != 0) {
-    mRespondingSessionIds.Put(aWindowId, new nsString(aSessionId));
-    mRespondingWindowIds.Put(aSessionId, aWindowId);
+  AddRespondingSessionId(aWindowId, aSessionId);
+
+  nsCOMPtr<nsIPresentationRespondingListener> listener;
+  if (mRespondingListeners.Get(aWindowId, getter_AddRefs(listener))) {
+    nsresult rv = listener->NotifySessionConnect(aWindowId, aSessionId);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
   }
 
   return static_cast<PresentationPresentingInfo*>(info.get())->NotifyResponderReady();
 }
 
 NS_IMETHODIMP
 PresentationService::UntrackSessionInfo(const nsAString& aSessionId,
                                         uint8_t aRole)
@@ -659,33 +662,26 @@ PresentationService::UntrackSessionInfo(
   // Remove the session info.
   if (nsIPresentationService::ROLE_CONTROLLER == aRole) {
     mSessionInfoAtController.Remove(aSessionId);
   } else {
     mSessionInfoAtReceiver.Remove(aSessionId);
   }
 
   // Remove the in-process responding info if there's still any.
-  uint64_t windowId = 0;
-  if (mRespondingWindowIds.Get(aSessionId, &windowId)) {
-    mRespondingWindowIds.Remove(aSessionId);
-    mRespondingSessionIds.Remove(windowId);
-  }
+  RemoveRespondingSessionId(aSessionId);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationService::GetWindowIdBySessionId(const nsAString& aSessionId,
                                             uint64_t* aWindowId)
 {
-  if (mRespondingWindowIds.Get(aSessionId, aWindowId)) {
-    return NS_OK;
-  }
-  return NS_ERROR_NOT_AVAILABLE;
+  return GetWindowIdBySessionIdInternal(aSessionId, aWindowId);
 }
 
 bool
 PresentationService::IsSessionAccessible(const nsAString& aSessionId,
                                          const uint8_t aRole,
                                          base::ProcessId aProcessId)
 {
   MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER ||
--- a/dom/presentation/PresentationService.h
+++ b/dom/presentation/PresentationService.h
@@ -4,33 +4,34 @@
  * 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_PresentationService_h
 #define mozilla_dom_PresentationService_h
 
 #include "nsCOMPtr.h"
 #include "nsIObserver.h"
-#include "nsRefPtrHashtable.h"
 #include "nsTObserverArray.h"
+#include "PresentationServiceBase.h"
 #include "PresentationSessionInfo.h"
 
 class nsIPresentationSessionRequest;
 class nsIURI;
 
 namespace mozilla {
 namespace dom {
 
 class PresentationRespondingInfo;
 
 class PresentationService final : public nsIPresentationService
                                 , public nsIObserver
+                                , public PresentationServiceBase
 {
 public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIOBSERVER
   NS_DECL_NSIPRESENTATIONSERVICE
 
   PresentationService();
   bool Init();
 
   already_AddRefed<PresentationSessionInfo>
   GetSessionInfo(const nsAString& aSessionId, const uint8_t aRole)
@@ -48,41 +49,25 @@ public:
     }
   }
 
   bool IsSessionAccessible(const nsAString& aSessionId,
                            const uint8_t aRole,
                            base::ProcessId aProcessId);
 
 private:
-  ~PresentationService();
+  virtual ~PresentationService();
   void HandleShutdown();
   nsresult HandleDeviceChange();
   nsresult HandleSessionRequest(nsIPresentationSessionRequest* aRequest);
   void NotifyAvailableChange(bool aIsAvailable);
   bool IsAppInstalled(nsIURI* aUri);
 
   bool mIsAvailable;
   nsTObserverArray<nsCOMPtr<nsIPresentationAvailabilityListener>> mAvailabilityListeners;
-
-  // Store the responding listener based on the window ID of the (in-process or
-  // OOP) receiver page.
-  // TODO Bug 1195605 - Support many-to-one session.
-  // So far responding listeners are registered but |notifySessionConnect| hasn't
-  // been called in any place until many-to-one session becomes supported.
-  nsRefPtrHashtable<nsUint64HashKey, nsIPresentationRespondingListener> mRespondingListeners;
-
   nsRefPtrHashtable<nsStringHashKey, PresentationSessionInfo> mSessionInfoAtController;
   nsRefPtrHashtable<nsStringHashKey, PresentationSessionInfo> mSessionInfoAtReceiver;
-
-  // Store the mapping between the window ID of the in-process page and the ID
-  // of the responding session. It's used for an in-process receiver page to
-  // retrieve the correspondent session ID. Besides, also keep the mapping
-  // between the responding session ID and the window ID to help look up the
-  // window ID.
-  nsClassHashtable<nsUint64HashKey, nsString> mRespondingSessionIds;
-  nsDataHashtable<nsStringHashKey, uint64_t> mRespondingWindowIds;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationService_h
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationServiceBase.cpp
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PresentationServiceBase.h"
+
+#include "nsString.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS0(PresentationServiceBase)
+
+nsresult
+PresentationServiceBase::GetExistentSessionIdAtLaunchInternal(
+  uint64_t aWindowId,
+  nsAString& aSessionId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsTArray<nsString>* sessionIdArray;
+  if (mRespondingSessionIds.Get(aWindowId, &sessionIdArray) &&
+      !sessionIdArray->IsEmpty()) {
+    aSessionId.Assign((*sessionIdArray)[0]);
+  }
+  else {
+    aSessionId.Truncate();
+  }
+  return NS_OK;
+}
+
+nsresult
+PresentationServiceBase::GetWindowIdBySessionIdInternal(
+  const nsAString& aSessionId,
+  uint64_t* aWindowId)
+{
+  if (mRespondingWindowIds.Get(aSessionId, aWindowId)) {
+    return NS_OK;
+  }
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+void
+PresentationServiceBase::AddRespondingSessionId(uint64_t aWindowId,
+                                                const nsAString& aSessionId)
+{
+  if (NS_WARN_IF(aWindowId == 0)) {
+    return;
+  }
+
+  nsTArray<nsString>* sessionIdArray;
+  if (!mRespondingSessionIds.Get(aWindowId, &sessionIdArray)) {
+    sessionIdArray = new nsTArray<nsString>();
+    mRespondingSessionIds.Put(aWindowId, sessionIdArray);
+  }
+
+  sessionIdArray->AppendElement(nsString(aSessionId));
+  mRespondingWindowIds.Put(aSessionId, aWindowId);
+}
+
+void
+PresentationServiceBase::RemoveRespondingSessionId(const nsAString& aSessionId)
+{
+  uint64_t windowId = 0;
+  if (mRespondingWindowIds.Get(aSessionId, &windowId)) {
+    mRespondingWindowIds.Remove(aSessionId);
+    nsTArray<nsString>* sessionIdArray;
+    if (mRespondingSessionIds.Get(windowId, &sessionIdArray)) {
+      sessionIdArray->RemoveElement(nsString(aSessionId));
+      if (sessionIdArray->IsEmpty()) {
+        mRespondingSessionIds.Remove(windowId);
+      }
+    }
+  }
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationServiceBase.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PresentationServiceBase_h
+#define mozilla_dom_PresentationServiceBase_h
+
+#include "nsRefPtrHashtable.h"
+#include "nsTArray.h"
+
+class nsIPresentationRespondingListener;
+class nsString;
+
+namespace mozilla {
+namespace dom {
+
+class PresentationServiceBase : public nsISupports
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  PresentationServiceBase() = default;
+
+protected:
+  virtual ~PresentationServiceBase() = default;
+
+  void Shutdown()
+  {
+    mRespondingListeners.Clear();
+    mRespondingSessionIds.Clear();
+    mRespondingWindowIds.Clear();
+  }
+  nsresult GetExistentSessionIdAtLaunchInternal(uint64_t aWindowId, nsAString& aSessionId);
+  nsresult GetWindowIdBySessionIdInternal(const nsAString& aSessionId, uint64_t* aWindowId);
+  void AddRespondingSessionId(uint64_t aWindowId, const nsAString& aSessionId);
+  void RemoveRespondingSessionId(const nsAString& aSessionId);
+
+  // Store the responding listener based on the window ID of the (in-process or
+  // OOP) receiver page.
+  nsRefPtrHashtable<nsUint64HashKey, nsIPresentationRespondingListener> mRespondingListeners;
+
+  // Store the mapping between the window ID of the in-process and OOP page and the ID
+  // of the responding session. It's used for an receiver page to retrieve
+  // the correspondent session ID. Besides, also keep the mapping
+  // between the responding session ID and the window ID to help look up the
+  // window ID.
+  nsClassHashtable<nsUint64HashKey, nsTArray<nsString>> mRespondingSessionIds;
+  nsDataHashtable<nsStringHashKey, uint64_t> mRespondingWindowIds;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PresentationServiceBase_h
--- a/dom/presentation/ipc/PPresentation.ipdl
+++ b/dom/presentation/ipc/PPresentation.ipdl
@@ -13,16 +13,17 @@ namespace mozilla {
 namespace dom {
 
 struct StartSessionRequest
 {
   nsString url;
   nsString sessionId;
   nsString origin;
   nsString deviceId;
+  uint64_t windowId;
 };
 
 struct SendSessionMessageRequest
 {
   nsString sessionId;
   uint8_t role;
   nsString data;
 };
@@ -67,13 +68,13 @@ parent:
   async RegisterSessionHandler(nsString aSessionId, uint8_t aRole);
   async UnregisterSessionHandler(nsString aSessionId, uint8_t aRole);
 
   async RegisterRespondingHandler(uint64_t aWindowId);
   async UnregisterRespondingHandler(uint64_t aWindowId);
 
   async PPresentationRequest(PresentationIPCRequest aRequest);
 
-  async NotifyReceiverReady(nsString aSessionId);
+  async NotifyReceiverReady(nsString aSessionId, uint64_t aWindowId);
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/presentation/ipc/PresentationIPCService.cpp
+++ b/dom/presentation/ipc/PresentationIPCService.cpp
@@ -18,55 +18,57 @@ using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
 namespace {
 
 PresentationChild* sPresentationChild;
 
 } // anonymous
 
-NS_IMPL_ISUPPORTS(PresentationIPCService, nsIPresentationService)
+NS_IMPL_ISUPPORTS_INHERITED(PresentationIPCService,
+                            PresentationServiceBase,
+                            nsIPresentationService)
 
 PresentationIPCService::PresentationIPCService()
 {
   ContentChild* contentChild = ContentChild::GetSingleton();
   if (NS_WARN_IF(!contentChild)) {
     return;
   }
   sPresentationChild = new PresentationChild(this);
   NS_WARN_IF(!contentChild->SendPPresentationConstructor(sPresentationChild));
 }
 
 /* virtual */
 PresentationIPCService::~PresentationIPCService()
 {
+  Shutdown();
+
   mAvailabilityListeners.Clear();
   mSessionListeners.Clear();
-  mRespondingSessionIds.Clear();
-  mRespondingWindowIds.Clear();
   sPresentationChild = nullptr;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::StartSession(const nsAString& aUrl,
                                      const nsAString& aSessionId,
                                      const nsAString& aOrigin,
                                      const nsAString& aDeviceId,
                                      uint64_t aWindowId,
                                      nsIPresentationServiceCallback* aCallback)
 {
   if (aWindowId != 0) {
-    mRespondingSessionIds.Put(aWindowId, new nsString(aSessionId));
-    mRespondingWindowIds.Put(aSessionId, aWindowId);
+    AddRespondingSessionId(aWindowId, aSessionId);
   }
 
   return SendRequest(aCallback, StartSessionRequest(nsString(aUrl),
                                                     nsString(aSessionId),
                                                     nsString(aOrigin),
-                                                    nsString(aDeviceId)));
+                                                    nsString(aDeviceId),
+                                                    aWindowId));
 }
 
 NS_IMETHODIMP
 PresentationIPCService::SendSessionMessage(const nsAString& aSessionId,
                                            uint8_t aRole,
                                            const nsAString& aData)
 {
   MOZ_ASSERT(!aSessionId.IsEmpty());
@@ -186,20 +188,17 @@ PresentationIPCService::UnregisterRespon
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::GetWindowIdBySessionId(const nsAString& aSessionId,
                                                uint64_t* aWindowId)
 {
-  if (mRespondingWindowIds.Get(aSessionId, aWindowId)) {
-    return NS_OK;
-  }
-  return NS_ERROR_NOT_AVAILABLE;
+  return GetWindowIdBySessionIdInternal(aSessionId, aWindowId);
 }
 
 nsresult
 PresentationIPCService::NotifySessionStateChange(const nsAString& aSessionId,
                                                  uint16_t aState)
 {
   nsCOMPtr<nsIPresentationSessionListener> listener;
   if (NS_WARN_IF(!mSessionListeners.Get(aSessionId, getter_AddRefs(listener)))) {
@@ -244,61 +243,48 @@ PresentationIPCService::NotifyAvailableC
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::GetExistentSessionIdAtLaunch(uint64_t aWindowId,
                                                      nsAString& aSessionId)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsString* sessionId = mRespondingSessionIds.Get(aWindowId);
-  if (sessionId) {
-    aSessionId.Assign(*sessionId);
-  } else {
-    aSessionId.Truncate();
-  }
-  return NS_OK;
+  return GetExistentSessionIdAtLaunchInternal(aWindowId, aSessionId);;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::NotifyReceiverReady(const nsAString& aSessionId,
                                             uint64_t aWindowId)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // No actual window uses 0 as its ID.
   if (NS_WARN_IF(aWindowId == 0)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   // Track the responding info for an OOP receiver page.
-  mRespondingSessionIds.Put(aWindowId, new nsString(aSessionId));
-  mRespondingWindowIds.Put(aSessionId, aWindowId);
+  AddRespondingSessionId(aWindowId, aSessionId);
 
-  NS_WARN_IF(!sPresentationChild->SendNotifyReceiverReady(nsString(aSessionId)));
+  NS_WARN_IF(!sPresentationChild->SendNotifyReceiverReady(nsString(aSessionId),
+                                                          aWindowId));
 
   // Release mCallback after using aSessionId
   // because aSessionId is held by mCallback.
   mCallback = nullptr;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::UntrackSessionInfo(const nsAString& aSessionId,
                                            uint8_t aRole)
 {
   // Remove the OOP responding info (if it has never been used).
-  uint64_t windowId = 0;
-  if (mRespondingWindowIds.Get(aSessionId, &windowId)) {
-    mRespondingWindowIds.Remove(aSessionId);
-    mRespondingSessionIds.Remove(windowId);
-  }
-
+  RemoveRespondingSessionId(aSessionId);
   return NS_OK;
 }
 
 void
 PresentationIPCService::NotifyPresentationChildDestroyed()
 {
   sPresentationChild = nullptr;
 }
--- a/dom/presentation/ipc/PresentationIPCService.h
+++ b/dom/presentation/ipc/PresentationIPCService.h
@@ -2,32 +2,33 @@
 /* vim: set sw=2 ts=8 et ft=cpp : */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_PresentationIPCService_h
 #define mozilla_dom_PresentationIPCService_h
 
+#include "mozilla/dom/PresentationServiceBase.h"
 #include "nsIPresentationService.h"
-#include "nsRefPtrHashtable.h"
 #include "nsTObserverArray.h"
 
 class nsIDocShell;
 
 namespace mozilla {
 namespace dom {
 
 class PresentationIPCRequest;
 class PresentationResponderLoadingCallback;
 
 class PresentationIPCService final : public nsIPresentationService
+                                   , public PresentationServiceBase
 {
 public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIPRESENTATIONSERVICE
 
   PresentationIPCService();
 
   nsresult NotifyAvailableChange(bool aAvailable);
 
   nsresult NotifySessionStateChange(const nsAString& aSessionId,
                                     uint16_t aState);
@@ -45,24 +46,15 @@ public:
 
 private:
   virtual ~PresentationIPCService();
   nsresult SendRequest(nsIPresentationServiceCallback* aCallback,
                        const PresentationIPCRequest& aRequest);
 
   nsTObserverArray<nsCOMPtr<nsIPresentationAvailabilityListener> > mAvailabilityListeners;
   nsRefPtrHashtable<nsStringHashKey, nsIPresentationSessionListener> mSessionListeners;
-  nsRefPtrHashtable<nsUint64HashKey, nsIPresentationRespondingListener> mRespondingListeners;
   RefPtr<PresentationResponderLoadingCallback> mCallback;
-
-  // Store the mapping between the window ID of the OOP page (in this process)
-  // and the ID of the responding session. It's used for an OOP receiver page
-  // to retrieve the correspondent session ID. Besides, also keep the mapping
-  // between the responding session ID and the window ID to help look up the
-  // window ID.
-  nsClassHashtable<nsUint64HashKey, nsString> mRespondingSessionIds;
-  nsDataHashtable<nsStringHashKey, uint64_t> mRespondingWindowIds;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationIPCService_h
--- a/dom/presentation/ipc/PresentationParent.cpp
+++ b/dom/presentation/ipc/PresentationParent.cpp
@@ -227,22 +227,23 @@ PresentationParent::NotifySessionConnect
   if (NS_WARN_IF(mActorDestroyed ||
                  !SendNotifySessionConnect(aWindowId, nsString(aSessionId)))) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 bool
-PresentationParent::RecvNotifyReceiverReady(const nsString& aSessionId)
+PresentationParent::RecvNotifyReceiverReady(const nsString& aSessionId,
+                                            const uint64_t& aWindowId)
 {
   MOZ_ASSERT(mService);
 
   // Set window ID to 0 since the window is from content process.
-  NS_WARN_IF(NS_FAILED(mService->NotifyReceiverReady(aSessionId, 0)));
+  NS_WARN_IF(NS_FAILED(mService->NotifyReceiverReady(aSessionId, aWindowId)));
   return true;
 }
 
 /*
  * Implementation of PresentationRequestParent
  */
 
 NS_IMPL_ISUPPORTS(PresentationRequestParent, nsIPresentationServiceCallback)
@@ -268,17 +269,18 @@ PresentationRequestParent::ActorDestroy(
 
 nsresult
 PresentationRequestParent::DoRequest(const StartSessionRequest& aRequest)
 {
   MOZ_ASSERT(mService);
 
   // Set window ID to 0 since the window is from content process.
   return mService->StartSession(aRequest.url(), aRequest.sessionId(),
-                                aRequest.origin(), aRequest.deviceId(), 0, this);
+                                aRequest.origin(), aRequest.deviceId(),
+                                aRequest.windowId(), this);
 }
 
 nsresult
 PresentationRequestParent::DoRequest(const SendSessionMessageRequest& aRequest)
 {
   MOZ_ASSERT(mService);
 
   // Validate the accessibility (primarily for receiver side) so that a
--- a/dom/presentation/ipc/PresentationParent.h
+++ b/dom/presentation/ipc/PresentationParent.h
@@ -53,17 +53,18 @@ public:
 
   virtual bool RecvUnregisterSessionHandler(const nsString& aSessionId,
                                             const uint8_t& aRole) override;
 
   virtual bool RecvRegisterRespondingHandler(const uint64_t& aWindowId) override;
 
   virtual bool RecvUnregisterRespondingHandler(const uint64_t& aWindowId) override;
 
-  virtual bool RecvNotifyReceiverReady(const nsString& aSessionId) override;
+  virtual bool RecvNotifyReceiverReady(const nsString& aSessionId,
+                                       const uint64_t& aWindowId) override;
 
 private:
   virtual ~PresentationParent();
 
   bool mActorDestroyed;
   nsCOMPtr<nsIPresentationService> mService;
   nsTArray<nsString> mSessionIdsAtController;
   nsTArray<nsString> mSessionIdsAtReceiver;
--- a/dom/presentation/moz.build
+++ b/dom/presentation/moz.build
@@ -17,32 +17,34 @@ EXPORTS.mozilla.dom += [
     'Presentation.h',
     'PresentationAvailability.h',
     'PresentationCallbacks.h',
     'PresentationConnection.h',
     'PresentationDeviceManager.h',
     'PresentationReceiver.h',
     'PresentationRequest.h',
     'PresentationService.h',
+    'PresentationServiceBase.h',
     'PresentationSessionInfo.h',
     'PresentationTCPSessionTransport.h',
 ]
 
 UNIFIED_SOURCES += [
     'ipc/PresentationChild.cpp',
     'ipc/PresentationIPCService.cpp',
     'ipc/PresentationParent.cpp',
     'Presentation.cpp',
     'PresentationAvailability.cpp',
     'PresentationCallbacks.cpp',
     'PresentationConnection.cpp',
     'PresentationDeviceManager.cpp',
     'PresentationReceiver.cpp',
     'PresentationRequest.cpp',
     'PresentationService.cpp',
+    'PresentationServiceBase.cpp',
     'PresentationSessionInfo.cpp',
     'PresentationSessionRequest.cpp',
     'PresentationTCPSessionTransport.cpp',
 ]
 
 EXTRA_COMPONENTS += [
     'PresentationDataChannelSessionTransport.js',
     'PresentationDataChannelSessionTransport.manifest',
--- a/dom/security/nsContentSecurityManager.cpp
+++ b/dom/security/nsContentSecurityManager.cpp
@@ -87,16 +87,19 @@ DoCheckLoadURIChecks(nsIURI* aURI, nsILo
 
   nsresult rv = NS_OK;
 
   nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadInfo->LoadingPrincipal();
   uint32_t flags = nsIScriptSecurityManager::STANDARD;
   if (aLoadInfo->GetAllowChrome()) {
     flags |= nsIScriptSecurityManager::ALLOW_CHROME;
   }
+  if (aLoadInfo->GetDisallowScript()) {
+    flags |= nsIScriptSecurityManager::DISALLOW_SCRIPT;
+  }
 
   bool isImageInEditorType = IsImageLoadInEditorAppType(aLoadInfo);
 
   // We don't have a loadingPrincipal for TYPE_DOCUMENT
   if (aLoadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_DOCUMENT &&
       !isImageInEditorType) {
     rv = nsContentUtils::GetSecurityManager()->
       CheckLoadURIWithPrincipal(loadingPrincipal,
--- a/dom/tests/mochitest/general/mochitest.ini
+++ b/dom/tests/mochitest/general/mochitest.ini
@@ -61,16 +61,17 @@ skip-if = (buildapp == 'b2g' && toolkit 
 [test_bug628069_2.html]
 [test_bug631440.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_bug653364.html]
 [test_bug861217.html]
 [test_clientRects.html]
 [test_clipboard_disallowed.html]
 [test_clipboard_events.html]
+subsuite = clipboard
 skip-if = buildapp == 'b2g' # b2g(clipboard undefined) b2g-debug(clipboard undefined) b2g-desktop(clipboard undefined)
 [test_consoleAPI.html]
 [test_DOMMatrix.html]
 [test_domWindowUtils.html]
 [test_domWindowUtils_scrollXY.html]
 [test_domWindowUtils_scrollbarSize.html]
 [test_donottrack.html]
 skip-if = buildapp == 'mulet'
@@ -115,17 +116,19 @@ run-if = e10s
 [test_windowProperties.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_windowedhistoryframes.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_navigation_timing.html]
 skip-if = buildapp == 'b2g' || buildapp == 'mulet'
 [test_bug1012662_editor.html]
 skip-if = (toolkit == 'android') # Disabled on Android, see bug 1230231
+subsuite = clipboard
 [test_bug1012662_noeditor.html]
+subsuite = clipboard
 skip-if = (toolkit == 'android') # Disabled on Android, see bug 1230231
 [test_bug1161721.html]
 [test_bug1170911.html]
 [test_storagePermissionsAccept.html]
 skip-if = buildapp == 'b2g' # Bug 1184427 - no SSL certs on b2g
 [test_storagePermissionsRejectForeign.html]
 skip-if = buildapp == 'b2g' # Bug 1184427 - no SSL certs on b2g
 [test_storagePermissionsReject.html]
--- a/dom/tests/unit/test_Fetch.js
+++ b/dom/tests/unit/test_Fetch.js
@@ -115,17 +115,17 @@ add_test(function test_GetData() {
   }).catch(function(e){
     do_report_unexpected_exception(e);
     do_test_finished();
     run_next_test();
   });
 });
 
 // test a GET with no init
-add_test(function test_GetData() {
+add_test(function test_GetDataNoInit() {
   do_test_pending();
 
   let testData = createTestData("/getData");
 
   server.registerPathHandler(testData.testPath, function(aRequest, aResponse) {
     try {
       // send a response
       writeDataToResponse(testData.response, aResponse);
@@ -198,17 +198,17 @@ add_test(function test_get50x() {
     run_next_test();
   });
 });
 
 // test a failure to connect
 add_test(function test_getTestFailedConnect() {
   do_test_pending();
   // try a server that's not there
-  fetch("ftp://localhost/should/fail").then(response => {
+  fetch("http://localhost:4/should/fail").then(response => {
     do_throw("Request should not succeed");
   }).catch(err => {
     do_check_eq(true, err instanceof TypeError);
     do_test_finished();
     run_next_test();
   });
 });
 
@@ -251,17 +251,17 @@ add_test(function test_PostJSONData() {
   }).catch(function(e) {
     do_report_unexpected_exception(e);
     do_test_finished();
     run_next_test();
   });
 });
 
 // test POSTing some text
-add_test(function test_PostJSONData() {
+add_test(function test_PostTextData() {
   do_test_pending();
 
   let testData = createTestData("/postTextData");
   testData.request.body = "A plain text body";
   testData.request.contentType = "text/plain";
   let responseHeaderName = "some-response-header";
   testData.response.headers[responseHeaderName] = "some header value";
 
--- a/dom/webidl/Node.webidl
+++ b/dom/webidl/Node.webidl
@@ -29,16 +29,18 @@ interface Node : EventTarget {
   [Constant]
   readonly attribute unsigned short nodeType;
   [Pure]
   readonly attribute DOMString nodeName;
 
   [Pure]
   readonly attribute DOMString? baseURI;
 
+  [Pure, BinaryName=getComposedDoc]
+  readonly attribute boolean isConnected;
   [Pure]
   readonly attribute Document? ownerDocument;
   [Pure, Pref="dom.node.rootNode.enabled"]
   readonly attribute Node rootNode;
   [Pure]
   readonly attribute Node? parentNode;
   [Pure]
   readonly attribute Element? parentElement;
--- a/editor/libeditor/tests/browserscope/mochitest.ini
+++ b/editor/libeditor/tests/browserscope/mochitest.ini
@@ -49,11 +49,12 @@ support-files =
   lib/richtext/LICENSE
   lib/richtext/README.Mozilla
   lib/richtext/richtext/editable.html
   lib/richtext/richtext/richtext.html
   lib/richtext/richtext/js/range.js
   lib/richtext/currentStatus.js
 
 [test_richtext2.html]
+subsuite = clipboard
 skip-if = os == 'android' && debug # Bug 1202045
 [test_richtext.html]
 
--- a/editor/libeditor/tests/chrome.ini
+++ b/editor/libeditor/tests/chrome.ini
@@ -4,33 +4,37 @@ support-files = green.png
 
 [test_bug46555.html]
 [test_bug366682.html]
 support-files = spellcheck.js
 [test_bug471319.html]
 [test_bug483651.html]
 [test_bug489202.xul]
 [test_bug490879.xul]
+subsuite = clipboard
 [test_bug569988.html]
 skip-if = buildapp == 'mulet'
 [test_bug599983.xul]
 skip-if = buildapp == 'mulet'
 [test_bug607584.xul]
 [test_bug616590.xul]
 [test_bug635636.html]
 [test_bug636465.xul]
 [test_bug646194.xul]
 [test_bug780908.xul]
 [test_bug830600.html]
+subsuite = clipboard
 [test_bug1053048.html]
 [test_bug1100966.html]
 [test_bug1102906.html]
 [test_bug1101392.html]
+subsuite = clipboard
 [test_bug1140105.html]
 [test_bug1140617.xul]
+subsuite = clipboard
 [test_bug1153237.html]
 [test_bug1154791.html]
 [test_bug1248128.html]
 [test_bug1248185.html]
 [test_bug1250010.html]
 [test_bug1257363.html]
 [test_composition_event_created_in_chrome.html]
 [test_contenteditable_text_input_handling.html]
--- a/editor/libeditor/tests/mochitest.ini
+++ b/editor/libeditor/tests/mochitest.ini
@@ -25,74 +25,84 @@ skip-if = os != "mac"
 [test_bug332636.html]
 support-files = test_bug332636.html^headers^
 [test_bug372345.html]
 skip-if = toolkit == 'android'
 [test_bug404320.html]
 [test_bug408231.html]
 skip-if = toolkit == 'android'
 [test_bug410986.html]
+subsuite = clipboard
 skip-if = toolkit == 'android'
 [test_bug414526.html]
 [test_bug417418.html]
 skip-if = android_version == '18' # bug 1147989
 [test_bug432225.html]
 skip-if = toolkit == 'android'
 [test_bug439808.html]
 [test_bug442186.html]
 [test_bug449243.html]
 [test_bug455992.html]
 [test_bug456244.html]
 [test_bug460740.html]
 [test_bug468353.html]
 [test_bug471722.html]
 [test_bug478725.html]
+subsuite = clipboard
 skip-if = toolkit == 'android'
 [test_bug480647.html]
 [test_bug480972.html]
+subsuite = clipboard
 skip-if = toolkit == 'android'
 [test_bug484181.html]
 skip-if = toolkit == 'android'
 [test_bug487524.html]
 [test_bug502673.html]
 [test_bug514156.html]
 [test_bug520189.html]
+subsuite = clipboard
 skip-if = toolkit == 'android'
 [test_bug525389.html]
+subsuite = clipboard
 skip-if = toolkit == 'android'
 [test_bug537046.html]
 [test_bug549262.html]
 skip-if = toolkit == 'android'
 [test_bug550434.html]
 skip-if = android_version == '18' # bug 1147989
 [test_bug551704.html]
+subsuite = clipboard
 [test_bug552782.html]
 [test_bug567213.html]
 [test_bug570144.html]
 [test_bug578771.html]
 skip-if = android_version == '18' # bug 1147989
 [test_bug586662.html]
 skip-if = toolkit == 'android'
 [test_bug587461.html]
 [test_bug590554.html]
 [test_bug592592.html]
 [test_bug596001.html]
+subsuite = clipboard
 [test_bug596333.html]
 skip-if = toolkit == 'android'
 [test_bug596506.html]
 [test_bug597331.html]
 skip-if = buildapp == 'mulet' || toolkit == 'android' || asan || (os == "win" && os_version != "5.1") # Bug 718316, Bug 1211213
 [test_bug597784.html]
 [test_bug599322.html]
+subsuite = clipboard
 skip-if = toolkit == 'android'
 [test_bug599983.html]
 [test_bug600570.html]
+subsuite = clipboard
 skip-if = toolkit == 'android' || (os == "win" && os_version != "5.1") # Bug 718316
 [test_bug602130.html]
 [test_bug603556.html]
+subsuite = clipboard
 [test_bug604532.html]
 skip-if = toolkit == 'android'
 [test_bug607584.html]
 [test_bug611182.html]
 skip-if = toolkit == 'android'
 [test_bug612128.html]
 [test_bug612447.html]
 [test_bug620906.html]
@@ -105,21 +115,23 @@ skip-if = toolkit == 'android' #bug 9577
 [test_bug640321.html]
 skip-if = android_version == '18' # bug 1147989
 [test_bug641466.html]
 [test_bug645914.html]
 [test_bug668599.html]
 [test_bug674770-1.html]
 skip-if = toolkit == 'android' || (os == 'linux' && e10s && (debug||asan)) # Bug 972110
 [test_bug674770-2.html]
+subsuite = clipboard
 skip-if = toolkit == 'android'
 [test_bug674861.html]
 [test_bug676401.html]
 [test_bug677752.html]
 [test_bug681229.html]
+subsuite = clipboard
 [test_bug686203.html]
 [test_bug692520.html]
 [test_bug697842.html]
 [test_bug725069.html]
 [test_bug735059.html]
 [test_bug738366.html]
 [test_bug740784.html]
 [test_bug742261.html]
@@ -143,29 +155,32 @@ skip-if = toolkit == 'android'
 skip-if = os != "win"
 [test_bug966552.html]
 skip-if = os != "win"
 [test_bug998188.html]
 [test_bug1026397.html]
 [test_bug1067255.html]
 [test_bug1094000.html]
 [test_CF_HTML_clipboard.html]
+subsuite = clipboard
 [test_contenteditable_focus.html]
 [test_dom_input_event_on_htmleditor.html]
 skip-if = toolkit == 'android' # bug 1054087
 [test_dom_input_event_on_texteditor.html]
 [test_keypress_untrusted_event.html]
 [test_root_element_replacement.html]
 [test_select_all_without_body.html]
 [test_spellcheck_pref.html]
 skip-if = toolkit == 'android'
 [test_bug1068979.html]
+subsuite = clipboard
 [test_bug1109465.html]
 [test_bug1162952.html]
 [test_bug1186799.html]
 [test_bug1181130-1.html]
 [test_bug1181130-2.html]
 [test_backspace_vs.html]
 [test_css_chrome_load_access.html]
 skip-if = toolkit == 'android' # chrome urls not available due to packaging
 [test_bug1247483.html]
+subsuite = clipboard
 skip-if = toolkit == 'android'
 [test_bug1258085.html]
--- a/gfx/cairo/cairo/src/cairo-quartz-font.c
+++ b/gfx/cairo/cairo/src/cairo-quartz-font.c
@@ -565,17 +565,17 @@ static cairo_int_status_t
 
     /* scale(1,-1) * font->base.scale */
     textMatrix = CGAffineTransformMake (font->base.scale.xx,
 					font->base.scale.yx,
 					-font->base.scale.xy,
 					-font->base.scale.yy,
 					0, 0);
 
-    ctFont = CTFontCreateWithGraphicsFont (font_face->cgFont, 0.0, NULL, NULL);
+    ctFont = CTFontCreateWithGraphicsFont (font_face->cgFont, 1.0, NULL, NULL);
     glyphPath = CTFontCreatePathForGlyph (ctFont, glyph, &textMatrix);
     CFRelease (ctFont);
     if (!glyphPath)
 	return CAIRO_INT_STATUS_UNSUPPORTED;
 
     path = _cairo_path_fixed_create ();
     if (!path) {
 	CGPathRelease (glyphPath);
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -2632,27 +2632,47 @@ gfxFontGroup::GetUnderlineOffset()
     return mUnderlineOffset;
 }
 
 already_AddRefed<gfxFont>
 gfxFontGroup::FindFontForChar(uint32_t aCh, uint32_t aPrevCh, uint32_t aNextCh,
                               Script aRunScript, gfxFont *aPrevMatchedFont,
                               uint8_t *aMatchType)
 {
-    // If the char is a cluster extender or NNBSP, we want to use the same
-    // font as the preceding character if possible. This is preferable to using
-    // the font group because it avoids breaks in shaping within a cluster.
-    const uint32_t NARROW_NO_BREAK_SPACE = 0x202f;
-    if (aPrevMatchedFont &&
-        (IsClusterExtender(aCh) || aCh == NARROW_NO_BREAK_SPACE) &&
+    // If the char is a cluster extender, we want to use the same font as the
+    // preceding character if possible. This is preferable to using the font
+    // group because it avoids breaks in shaping within a cluster.
+    if (aPrevMatchedFont && IsClusterExtender(aCh) &&
         aPrevMatchedFont->HasCharacter(aCh)) {
         RefPtr<gfxFont> ret = aPrevMatchedFont;
         return ret.forget();
     }
 
+    // Special cases for NNBSP (as used in Mongolian):
+    const uint32_t NARROW_NO_BREAK_SPACE = 0x202f;
+    if (aCh == NARROW_NO_BREAK_SPACE) {
+        // If there is no preceding character, try the font that we'd use
+        // for the next char (unless it's just another NNBSP; we don't try
+        // to look ahead through a whole run of them).
+        if (!aPrevCh && aNextCh && aNextCh != NARROW_NO_BREAK_SPACE) {
+            RefPtr<gfxFont> nextFont =
+                FindFontForChar(aNextCh, 0, 0, aRunScript, aPrevMatchedFont,
+                                aMatchType);
+            if (nextFont && nextFont->HasCharacter(aCh)) {
+                return nextFont.forget();
+            }
+        }
+        // Otherwise, treat NNBSP like a cluster extender (as above) and try
+        // to continue the preceding font run.
+        if (aPrevMatchedFont && aPrevMatchedFont->HasCharacter(aCh)) {
+            RefPtr<gfxFont> ret = aPrevMatchedFont;
+            return ret.forget();
+        }
+    }
+
     // To optimize common cases, try the first font in the font-group
     // before going into the more detailed checks below
     uint32_t nextIndex = 0;
     bool isJoinControl = gfxFontUtils::IsJoinControl(aCh);
     bool wasJoinCauser = gfxFontUtils::IsJoinCauser(aPrevCh);
     bool isVarSelector = gfxFontUtils::IsVarSelector(aCh);
 
     if (!isJoinControl && !wasJoinCauser && !isVarSelector) {
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -982,17 +982,18 @@ GeckoChildProcessHost::PerformAsyncLaunc
   bool shouldSandboxCurrentProcess = false;
 
   // XXX: Bug 1124167: We should get rid of the process specific logic for
   // sandboxing in this class at some point. Unfortunately it will take a bit
   // of reorganizing so I don't think this patch is the right time.
   switch (mProcessType) {
     case GeckoProcessType_Content:
 #if defined(MOZ_CONTENT_SANDBOX)
-      if (!PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX")) {
+      if (mSandboxLevel > 0 &&
+          !PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX")) {
         mSandboxBroker.SetSecurityLevelForContentProcess(mSandboxLevel);
         cmdLine.AppendLooseValue(UTF8ToWide("-sandbox"));
         shouldSandboxCurrentProcess = true;
         AddContentSandboxAllowedFiles(mSandboxLevel, mAllowedFilesRead);
       }
 #endif // MOZ_CONTENT_SANDBOX
       break;
     case GeckoProcessType_Plugin:
--- a/js/src/Makefile.in
+++ b/js/src/Makefile.in
@@ -11,20 +11,16 @@ make_min_ver := 3.81
 ifneq ($(make_min_ver),$(firstword $(sort $(make_min_ver) $(MAKE_VERSION))))
 $(error GNU Make $(make_min_ver) or higher is required)
 endif
 
 DASH_R		= -r
 
 # Define keyword generator before rules.mk, see bug 323979 comment 50
 
-GARBAGE += jsautokw.h host_jskwgen$(HOST_BIN_SUFFIX)
-
-GARBAGE += selfhosted.out.h
-
 USE_HOST_CXX = 1
 
 ifdef HAVE_DTRACE
 ifneq ($(OS_ARCH),Darwin)
 DTRACE_PROBE_OBJ = $(LIBRARY_NAME)-dtrace.$(OBJ_SUFFIX)
 endif
 MOZILLA_DTRACE_SRC = $(srcdir)/devtools/javascript-trace.d
 endif
@@ -215,24 +211,16 @@ endif
 ifneq (,$(SHARED_LIBRARY))
 	$(SYSINSTALL) $(SHARED_LIBRARY) $(DESTDIR)$(libdir)
 endif
 ifneq (,$(IMPORT_LIBRARY))
 	$(SYSINSTALL) $(IMPORT_LIBRARY) $(DESTDIR)$(libdir)
 endif
 	$(MAKE) -C shell install
 
-# Use CURDIR to avoid finding a jsautokw.h in the source tree (from a
-# previous build?) via VPATH when we're building in a separate tree.
-$(CURDIR)/jsautokw.h: host_jskwgen$(HOST_BIN_SUFFIX)
-	./host_jskwgen$(HOST_BIN_SUFFIX) $@
-
-# Force auto-header generation before compiling any source that may use them
-$(OBJS): $(CURDIR)/jsautokw.h
-
 ifdef HAVE_DTRACE
 javascript-trace.h: $(srcdir)/devtools/javascript-trace.d
 	dtrace -x nolibs -h -s $(srcdir)/devtools/javascript-trace.d -o javascript-trace.h.in
 	sed -e 's/if _DTRACE_VERSION/ifdef INCLUDE_MOZILLA_DTRACE/' \
 	    -e '/const/!s/char \*/const char */g' \
 	    javascript-trace.h.in > javascript-trace.h
 
 # We can't automatically generate dependencies on auto-generated headers;
new file mode 100644
--- /dev/null
+++ b/js/src/jsautokw.py
@@ -0,0 +1,22 @@
+# 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/.
+
+from __future__ import print_function
+
+import os
+import sys
+import subprocess
+
+def main(output, exe):
+    # moz.build passes in the exe name without any path, so to run it we need to
+    # prepend the './'
+    run_exe = exe if os.path.isabs(exe) else './%s' % exe
+
+    # Use universal_newlines so everything is '\n', which gets converted to
+    # '\r\n' when writing out the file in Windows.
+    data = subprocess.check_output([run_exe], universal_newlines=True)
+    output.write(data)
+
+if __name__ == '__main__':
+    main(sys.stdout, *sys.argv[1:])
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -398,33 +398,38 @@ JS_FRIEND_API(bool)
 js::RunningWithTrustedPrincipals(JSContext* cx)
 {
     return cx->runningWithTrustedPrincipals();
 }
 
 JS_FRIEND_API(JSFunction*)
 js::GetOutermostEnclosingFunctionOfScriptedCaller(JSContext* cx)
 {
-    ScriptFrameIter iter(cx, FrameIter::STOP_AT_SAVED);
+    ScriptFrameIter iter(cx, FrameIter::GO_THROUGH_SAVED);
 
     // Skip eval frames.
     while (!iter.done() && iter.isEvalFrame())
         ++iter;
 
     if (iter.done())
         return nullptr;
 
     if (!iter.isFunctionFrame())
         return nullptr;
 
+    if (iter.compartment() != cx->compartment())
+        return nullptr;
+
     RootedFunction curr(cx, iter.callee(cx));
     for (StaticScopeIter<NoGC> i(curr); !i.done(); i++) {
         if (i.type() == StaticScopeIter<NoGC>::Function)
             curr = &i.fun();
     }
+
+    assertSameCompartment(cx, curr);
     return curr;
 }
 
 JS_FRIEND_API(JSFunction*)
 js::DefineFunctionWithReserved(JSContext* cx, JSObject* objArg, const char* name, JSNative call,
                                unsigned nargs, unsigned attrs)
 {
     RootedObject obj(cx, objArg);
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -598,16 +598,23 @@ else:
     SOURCES += [
         'perf/pm_stub.cpp'
     ]
 
 HostSimplePrograms([
     'host_jskwgen',
 ])
 
+GENERATED_FILES += ['jsautokw.h']
+jsautokw = GENERATED_FILES['jsautokw.h']
+jsautokw.script = 'jsautokw.py'
+jsautokw.inputs += [
+    '!host_jskwgen%s' % CONFIG['HOST_BIN_SUFFIX'],
+]
+
 # JavaScript must be built shared, even for static builds, as it is used by
 # other modules which are always built shared. Failure to do so results in
 # the js code getting copied into xpinstall and jsd as well as mozilla-bin,
 # and then the static data cells used for locking no longer work.
 #
 # In fact, we now build both a static and a shared library, as the
 # JS shell would like to link to the static library.
 
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -2041,19 +2041,23 @@ nsCSSRendering::DetermineBackgroundColor
     if (aDrawBackgroundImage || !bg->IsTransparent()) {
       aDrawBackgroundColor = true;
     } else {
       bgColor = NS_RGBA(0,0,0,0);
     }
   }
 
   // We can skip painting the background color if a background image is opaque.
+  nsStyleImageLayers::Repeat repeat = bg->BottomLayer().mRepeat;
+  bool xFullRepeat = repeat.mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
+                     repeat.mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND;
+  bool yFullRepeat = repeat.mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
+                     repeat.mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND;
   if (aDrawBackgroundColor &&
-      bg->BottomLayer().mRepeat.mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT &&
-      bg->BottomLayer().mRepeat.mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT &&
+      xFullRepeat && yFullRepeat &&
       bg->BottomLayer().mImage.IsOpaque() &&
       bg->BottomLayer().mBlendMode == NS_STYLE_BLEND_NORMAL) {
     aDrawBackgroundColor = false;
   }
 
   return bgColor;
 }
 
@@ -3097,17 +3101,18 @@ nsCSSRendering::PaintBackgroundWithSC(co
             ctx->SetOp(co);
           }
 
           result &=
             state.mImageRenderer.DrawBackground(&aParams.presCtx,
                                                 aParams.renderingCtx,
                                                 state.mDestArea, state.mFillArea,
                                                 state.mAnchor + paintBorderArea.TopLeft(),
-                                                clipState.mDirtyRect);
+                                                clipState.mDirtyRect,
+                                                state.mRepeatSize);
 
           if (co != CompositionOp::OP_OVER) {
             ctx->SetOp(CompositionOp::OP_OVER);
           }
         }
       }
     }
   }
@@ -3220,51 +3225,124 @@ nsCSSRendering::ComputeImageLayerPositio
       }
     }
   }
   *aAttachedToFrame = attachedToFrame;
 
   return bgPositioningArea;
 }
 
+// Implementation of the formula for computation of background-repeat round
+// See http://dev.w3.org/csswg/css3-background/#the-background-size
+// This function returns the adjusted size of the background image.
+static nscoord
+ComputeRoundedSize(nscoord aCurrentSize, nscoord aPositioningSize)
+{
+  float repeatCount = NS_roundf(float(aPositioningSize) / float(aCurrentSize));
+  if (repeatCount < 1.0f) {
+    return aPositioningSize;
+  }
+  return nscoord(NS_lround(float(aPositioningSize) / repeatCount));
+}
+
 // Apply the CSS image sizing algorithm as it applies to background images.
 // See http://www.w3.org/TR/css3-background/#the-background-size .
 // aIntrinsicSize is the size that the background image 'would like to be'.
 // It can be found by calling nsImageRenderer::ComputeIntrinsicSize.
 static nsSize
 ComputeDrawnSizeForBackground(const CSSSizeOrRatio& aIntrinsicSize,
                               const nsSize& aBgPositioningArea,
-                              const nsStyleImageLayers::Size& aLayerSize)
+                              const nsStyleImageLayers::Size& aLayerSize,
+                              uint8_t aXRepeat, uint8_t aYRepeat)
 {
+  nsSize imageSize;
+
   // Size is dictated by cover or contain rules.
   if (aLayerSize.mWidthType == nsStyleImageLayers::Size::eContain ||
       aLayerSize.mWidthType == nsStyleImageLayers::Size::eCover) {
     nsImageRenderer::FitType fitType =
       aLayerSize.mWidthType == nsStyleImageLayers::Size::eCover
         ? nsImageRenderer::COVER
         : nsImageRenderer::CONTAIN;
-    return nsImageRenderer::ComputeConstrainedSize(aBgPositioningArea,
-                                                   aIntrinsicSize.mRatio,
-                                                   fitType);
-  }
-
-  // No cover/contain constraint, use default algorithm.
-  CSSSizeOrRatio specifiedSize;
-  if (aLayerSize.mWidthType == nsStyleImageLayers::Size::eLengthPercentage) {
-    specifiedSize.SetWidth(
-      aLayerSize.ResolveWidthLengthPercentage(aBgPositioningArea));
-  }
-  if (aLayerSize.mHeightType == nsStyleImageLayers::Size::eLengthPercentage) {
-    specifiedSize.SetHeight(
-      aLayerSize.ResolveHeightLengthPercentage(aBgPositioningArea));
-  }
-
-  return nsImageRenderer::ComputeConcreteSize(specifiedSize,
-                                              aIntrinsicSize,
-                                              aBgPositioningArea);
+    imageSize = nsImageRenderer::ComputeConstrainedSize(aBgPositioningArea,
+                                                        aIntrinsicSize.mRatio,
+                                                        fitType);
+  } else {
+    // No cover/contain constraint, use default algorithm.
+    CSSSizeOrRatio specifiedSize;
+    if (aLayerSize.mWidthType == nsStyleImageLayers::Size::eLengthPercentage) {
+      specifiedSize.SetWidth(
+        aLayerSize.ResolveWidthLengthPercentage(aBgPositioningArea));
+    }
+    if (aLayerSize.mHeightType == nsStyleImageLayers::Size::eLengthPercentage) {
+      specifiedSize.SetHeight(
+        aLayerSize.ResolveHeightLengthPercentage(aBgPositioningArea));
+    }
+
+    imageSize = nsImageRenderer::ComputeConcreteSize(specifiedSize,
+                                                     aIntrinsicSize,
+                                                     aBgPositioningArea);
+  }
+
+  // See https://www.w3.org/TR/css3-background/#background-size .
+  // "If 'background-repeat' is 'round' for one (or both) dimensions, there is a second
+  //  step. The UA must scale the image in that dimension (or both dimensions) so that
+  //  it fits a whole number of times in the background positioning area."
+  // "If 'background-repeat' is 'round' for one dimension only and if 'background-size'
+  //  is 'auto' for the other dimension, then there is a third step: that other dimension
+  //  is scaled so that the original aspect ratio is restored."
+  bool isRepeatRoundInBothDimensions = aXRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND &&
+                                       aYRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND;
+
+  // Calculate the rounded size only if the background-size computation
+  // returned a correct size for the image.
+  if (imageSize.width && aXRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND) {
+    imageSize.width = ComputeRoundedSize(imageSize.width, aBgPositioningArea.width);
+    if (!isRepeatRoundInBothDimensions &&
+        aLayerSize.mHeightType == nsStyleImageLayers::Size::DimensionType::eAuto) {
+      // Restore intrinsic rato
+      if (aIntrinsicSize.mRatio.width) {
+        float scale = float(aIntrinsicSize.mRatio.height) / aIntrinsicSize.mRatio.width;
+        imageSize.height = NSCoordSaturatingNonnegativeMultiply(imageSize.width, scale);
+      }
+    }
+  }
+
+  // Calculate the rounded size only if the background-size computation
+  // returned a correct size for the image.
+  if (imageSize.height && aYRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND) {
+    imageSize.height = ComputeRoundedSize(imageSize.height, aBgPositioningArea.height);
+    if (!isRepeatRoundInBothDimensions &&
+        aLayerSize.mWidthType == nsStyleImageLayers::Size::DimensionType::eAuto) {
+      // Restore intrinsic rato
+      if (aIntrinsicSize.mRatio.height) {
+        float scale = float(aIntrinsicSize.mRatio.width) / aIntrinsicSize.mRatio.height;
+        imageSize.width = NSCoordSaturatingNonnegativeMultiply(imageSize.height, scale);
+      }
+    }
+  }
+
+  return imageSize;
+}
+
+/* ComputeSpacedRepeatSize
+ * aImageDimension: the image width/height
+ * aAvailableSpace: the background positioning area width/height
+ * aRepeatSize: the image size plus gap size of app units for use as spacing
+ */
+static nscoord
+ComputeSpacedRepeatSize(nscoord aImageDimension,
+                        nscoord aAvailableSpace) {
+  float ratio = aAvailableSpace / aImageDimension;
+
+  if (ratio < 2.0f) { // If you can't repeat at least twice, then don't repeat.
+    return aImageDimension;
+  } else {
+    return (aAvailableSpace - aImageDimension) / (NSToIntFloor(ratio) - 1);
+  }
 }
 
 nsBackgroundLayerState
 nsCSSRendering::PrepareImageLayer(nsPresContext* aPresContext,
                                   nsIFrame* aForFrame,
                                   uint32_t aFlags,
                                   const nsRect& aBorderArea,
                                   const nsRect& aBGClipRect,
@@ -3379,50 +3457,80 @@ nsCSSRendering::PrepareImageLayer(nsPres
       // always clipped to the viewport when we draw to the screen. (But it's
       // not a pure optimization since it can affect the values of pixels at the
       // edge of the viewport --- whether they're sampled from a putative "next
       // tile" or not.)
       bgClipRect.IntersectRect(bgClipRect, bgPositioningArea + aBorderArea.TopLeft());
     }
   }
 
-  // Scale the image as specified for background-size and as required for
-  // proper background positioning when background-position is defined with
-  // percentages.
+  int repeatX = aLayer.mRepeat.mXRepeat;
+  int repeatY = aLayer.mRepeat.mYRepeat;
+
+  // Scale the image as specified for background-size and background-repeat.
+  // Also as required for proper background positioning when background-position
+  // is defined with percentages.
   CSSSizeOrRatio intrinsicSize = state.mImageRenderer.ComputeIntrinsicSize();
   nsSize bgPositionSize = bgPositioningArea.Size();
   nsSize imageSize = ComputeDrawnSizeForBackground(intrinsicSize,
                                                    bgPositionSize,
-                                                   aLayer.mSize);
+                                                   aLayer.mSize,
+                                                   repeatX,
+                                                   repeatY);
+
   if (imageSize.width <= 0 || imageSize.height <= 0)
     return state;
 
   state.mImageRenderer.SetPreferredSize(intrinsicSize,
                                         imageSize);
 
   // Compute the position of the background now that the background's size is
   // determined.
   nsImageRenderer::ComputeObjectAnchorPoint(aLayer.mPosition,
                                             bgPositionSize, imageSize,
                                             &imageTopLeft, &state.mAnchor);
+  state.mRepeatSize = imageSize;
+  if (repeatX == NS_STYLE_IMAGELAYER_REPEAT_SPACE) {
+    state.mRepeatSize.width = ComputeSpacedRepeatSize(imageSize.width,
+                                                      bgPositionSize.width);
+    if (state.mRepeatSize.width > imageSize.width) {
+      imageTopLeft.x = 0;
+      state.mAnchor.x = 0;
+    } else {
+      repeatX = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
+    }
+  }
+
+  if (repeatY == NS_STYLE_IMAGELAYER_REPEAT_SPACE) {
+    state.mRepeatSize.height = ComputeSpacedRepeatSize(imageSize.height,
+                                                       bgPositionSize.height);
+    if (state.mRepeatSize.height > imageSize.height) {
+      imageTopLeft.y = 0;
+      state.mAnchor.y = 0;
+    } else {
+      repeatY = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
+    }
+  }
+
   imageTopLeft += bgPositioningArea.TopLeft();
   state.mAnchor += bgPositioningArea.TopLeft();
-
   state.mDestArea = nsRect(imageTopLeft + aBorderArea.TopLeft(), imageSize);
   state.mFillArea = state.mDestArea;
-  int repeatX = aLayer.mRepeat.mXRepeat;
-  int repeatY = aLayer.mRepeat.mYRepeat;
 
   ExtendMode repeatMode = ExtendMode::CLAMP;
-  if (repeatX == NS_STYLE_IMAGELAYER_REPEAT_REPEAT) {
+  if (repeatX == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
+      repeatX == NS_STYLE_IMAGELAYER_REPEAT_ROUND ||
+      repeatX == NS_STYLE_IMAGELAYER_REPEAT_SPACE) {
     state.mFillArea.x = bgClipRect.x;
     state.mFillArea.width = bgClipRect.width;
     repeatMode = ExtendMode::REPEAT_X;
   }
-  if (repeatY == NS_STYLE_IMAGELAYER_REPEAT_REPEAT) {
+  if (repeatY == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
+      repeatY == NS_STYLE_IMAGELAYER_REPEAT_ROUND ||
+      repeatY == NS_STYLE_IMAGELAYER_REPEAT_SPACE) {
     state.mFillArea.y = bgClipRect.y;
     state.mFillArea.height = bgClipRect.height;
 
     /***
      * We're repeating on the X axis already,
      * so if we have to repeat in the Y axis,
      * we really need to repeat in both directions.
      */
@@ -5117,16 +5225,17 @@ RGBALuminanceOperation(uint8_t *aData,
 
 DrawResult
 nsImageRenderer::Draw(nsPresContext*       aPresContext,
                       nsRenderingContext&  aRenderingContext,
                       const nsRect&        aDirtyRect,
                       const nsRect&        aDest,
                       const nsRect&        aFill,
                       const nsPoint&       aAnchor,
+                      const nsSize&        aRepeatSize,
                       const CSSIntRect&    aSrc)
 {
   if (!IsReady()) {
     NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
     return DrawResult::TEMPORARY_ERROR;
   }
   if (aDest.IsEmpty() || aFill.IsEmpty() ||
       mSize.width <= 0 || mSize.height <= 0) {
@@ -5153,17 +5262,18 @@ nsImageRenderer::Draw(nsPresContext*    
     case eStyleImageType_Image:
     {
       CSSIntSize imageSize(nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
                            nsPresContext::AppUnitsToIntCSSPixels(mSize.height));
       result =
         nsLayoutUtils::DrawBackgroundImage(*ctx,
                                            aPresContext,
                                            mImageContainer, imageSize, filter,
-                                           aDest, aFill, aAnchor, aDirtyRect,
+                                           aDest, aFill, aRepeatSize,
+                                           aAnchor, aDirtyRect,
                                            ConvertImageRendererToDrawFlags(mFlags),
                                            mExtendMode);
       break;
     }
     case eStyleImageType_Gradient:
     {
       nsCSSRendering::PaintGradient(aPresContext, aRenderingContext,
                                     mGradientData, aDirtyRect,
@@ -5250,29 +5360,30 @@ nsImageRenderer::DrawableForElement(cons
 }
 
 DrawResult
 nsImageRenderer::DrawBackground(nsPresContext*       aPresContext,
                                 nsRenderingContext&  aRenderingContext,
                                 const nsRect&        aDest,
                                 const nsRect&        aFill,
                                 const nsPoint&       aAnchor,
-                                const nsRect&        aDirty)
+                                const nsRect&        aDirty,
+                                const nsSize&        aRepeatSize)
 {
   if (!IsReady()) {
     NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
     return DrawResult::TEMPORARY_ERROR;
   }
   if (aDest.IsEmpty() || aFill.IsEmpty() ||
       mSize.width <= 0 || mSize.height <= 0) {
     return DrawResult::SUCCESS;
   }
 
   return Draw(aPresContext, aRenderingContext,
-              aDirty, aDest, aFill, aAnchor,
+              aDirty, aDest, aFill, aAnchor, aRepeatSize,
               CSSIntRect(0, 0,
                          nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
                          nsPresContext::AppUnitsToIntCSSPixels(mSize.height)));
 }
 
 /**
  * Compute the size and position of the master copy of the image. I.e., a single
  * tile used to fill the dest rect.
@@ -5426,17 +5537,17 @@ nsImageRenderer::DrawBorderImageComponen
                                     drawFlags);
   }
 
   nsRect destTile = RequiresScaling(aFill, aHFill, aVFill, aUnitSize)
                   ? ComputeTile(aFill, aHFill, aVFill, aUnitSize)
                   : aFill;
 
   return Draw(aPresContext, aRenderingContext, aDirtyRect, destTile,
-              aFill, destTile.TopLeft(), aSrc);
+              aFill, destTile.TopLeft(), nsSize(0, 0), aSrc);
 }
 
 bool
 nsImageRenderer::IsRasterImage()
 {
   if (mType != eStyleImageType_Image || !mImageContainer)
     return false;
   return mImageContainer->GetType() == imgIContainer::TYPE_RASTER;
--- a/layout/base/nsCSSRendering.h
+++ b/layout/base/nsCSSRendering.h
@@ -212,17 +212,18 @@ public:
    * arguments.
    * @see nsLayoutUtils::DrawImage() for parameters.
    */
   DrawResult DrawBackground(nsPresContext*       aPresContext,
                             nsRenderingContext&  aRenderingContext,
                             const nsRect&        aDest,
                             const nsRect&        aFill,
                             const nsPoint&       aAnchor,
-                            const nsRect&        aDirty);
+                            const nsRect&        aDirty,
+                            const nsSize&        aRepeatSize);
 
   /**
    * Draw the image to a single component of a border-image style rendering.
    * aFill The destination rect to be drawn into
    * aSrc is the part of the image to be rendered into a tile (aUnitSize in
    * aFill), if aSrc and the dest tile are different sizes, the image will be
    * scaled to map aSrc onto the dest tile.
    * aHFill and aVFill are the repeat patterns for the component -
@@ -268,20 +269,21 @@ private:
    * @see nsLayoutUtils::DrawImage() for other parameters.
    */
   DrawResult Draw(nsPresContext*       aPresContext,
                   nsRenderingContext&  aRenderingContext,
                   const nsRect&        aDirtyRect,
                   const nsRect&        aDest,
                   const nsRect&        aFill,
                   const nsPoint&       aAnchor,
+                  const nsSize&        aRepeatSize,
                   const mozilla::CSSIntRect& aSrc);
 
   /**
-   * Helper method for creating a gfxDrawable from mPaintServerFrame or 
+   * Helper method for creating a gfxDrawable from mPaintServerFrame or
    * mImageElementSurface.
    * Requires mType is eStyleImageType_Element.
    * Returns null if we cannot create the drawable.
    */
   already_AddRefed<gfxDrawable> DrawableForElement(const nsRect& aImageRect,
                                                    nsRenderingContext&  aRenderingContext);
 
   nsIFrame*                 mForFrame;
@@ -331,16 +333,21 @@ struct nsBackgroundLayerState {
    */
   nsRect mFillArea;
   /**
    * The anchor point that should be snapped to a pixel corner. Same
    * coordinate system as aBorderArea/aBGClipRect passed into
    * PrepareImageLayer.
    */
   nsPoint mAnchor;
+  /**
+   * The background-repeat property space keyword computes the
+   * repeat size which is image size plus spacing.
+   */
+  nsSize mRepeatSize;
 };
 
 struct nsCSSRendering {
   typedef mozilla::gfx::CompositionOp CompositionOp;
   typedef mozilla::gfx::DrawTarget DrawTarget;
   typedef mozilla::gfx::Float Float;
   typedef mozilla::gfx::Point Point;
   typedef mozilla::gfx::Rect Rect;
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -2907,16 +2907,18 @@ nsDisplayBackgroundImage::GetOpaqueRegio
   // which expects frames to be sent to it in content order, not reverse
   // content order which we'll produce here.
   // Of course, if there's only one frame in the flow, it doesn't matter.
   if (mFrame->StyleBorder()->mBoxDecorationBreak ==
         NS_STYLE_BOX_DECORATION_BREAK_CLONE ||
       (!mFrame->GetPrevContinuation() && !mFrame->GetNextContinuation())) {
     const nsStyleImageLayers::Layer& layer = mBackgroundStyle->mImage.mLayers[mLayer];
     if (layer.mImage.IsOpaque() && layer.mBlendMode == NS_STYLE_BLEND_NORMAL &&
+        layer.mRepeat.mXRepeat != NS_STYLE_IMAGELAYER_REPEAT_SPACE &&
+        layer.mRepeat.mYRepeat != NS_STYLE_IMAGELAYER_REPEAT_SPACE &&
         layer.mClip != NS_STYLE_IMAGELAYER_CLIP_TEXT) {
       result = GetInsideClipRegion(this, layer.mClip, mBounds, mBackgroundRect);
     }
   }
 
   return result;
 }
 
@@ -4948,18 +4950,16 @@ nsDisplayFixedPosition::nsDisplayFixedPo
 {
   MOZ_COUNT_CTOR(nsDisplayFixedPosition);
   Init(aBuilder);
 }
 
 void
 nsDisplayFixedPosition::Init(nsDisplayListBuilder* aBuilder)
 {
-  bool snap;
-  mVisibleRect = GetBounds(aBuilder, &snap);
   mAnimatedGeometryRootForScrollMetadata = mAnimatedGeometryRoot;
   if (ShouldFixToViewport(aBuilder)) {
     mAnimatedGeometryRoot = aBuilder->FindAnimatedGeometryRootFor(this);
   }
 }
 
 /* static */ nsDisplayFixedPosition*
 nsDisplayFixedPosition::CreateForFixedBackground(nsDisplayListBuilder* aBuilder,
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -6673,17 +6673,16 @@ ComputeSnappedImageDrawingParameters(gfx
 
   ImageRegion region =
     ImageRegion::CreateWithSamplingRestriction(imageSpaceFill, subimage, extendMode);
 
   return SnappedImageDrawingParameters(transform, intImageSize,
                                        region, svgViewportSize);
 }
 
-
 static DrawResult
 DrawImageInternal(gfxContext&            aContext,
                   nsPresContext*         aPresContext,
                   imgIContainer*         aImage,
                   Filter                 aGraphicsFilter,
                   const nsRect&          aDest,
                   const nsRect&          aFill,
                   const nsPoint&         aAnchor,
@@ -6915,33 +6914,54 @@ nsLayoutUtils::ComputeSizeForDrawingWith
 /* static */ DrawResult
 nsLayoutUtils::DrawBackgroundImage(gfxContext&         aContext,
                                    nsPresContext*      aPresContext,
                                    imgIContainer*      aImage,
                                    const CSSIntSize&   aImageSize,
                                    Filter              aGraphicsFilter,
                                    const nsRect&       aDest,
                                    const nsRect&       aFill,
+                                   const nsSize&       aRepeatSize,
                                    const nsPoint&      aAnchor,
                                    const nsRect&       aDirty,
                                    uint32_t            aImageFlags,
                                    ExtendMode          aExtendMode)
 {
   PROFILER_LABEL("layout", "nsLayoutUtils::DrawBackgroundImage",
                  js::ProfileEntry::Category::GRAPHICS);
 
   if (UseBackgroundNearestFiltering()) {
     aGraphicsFilter = Filter::POINT;
   }
 
   SVGImageContext svgContext(aImageSize, Nothing());
 
-  return DrawImageInternal(aContext, aPresContext, aImage,
-                           aGraphicsFilter, aDest, aFill, aAnchor,
-                           aDirty, &svgContext, aImageFlags, aExtendMode);
+  /* Fast path when there is no need for image spacing */
+  if (aRepeatSize.width == aDest.width && aRepeatSize.height == aDest.height) {
+    return DrawImageInternal(aContext, aPresContext, aImage,
+                             aGraphicsFilter, aDest, aFill, aAnchor,
+                             aDirty, &svgContext, aImageFlags, aExtendMode);
+  }
+
+  nsPoint firstTilePos = aDest.TopLeft() +
+                         nsPoint(NSToIntFloor(float(aFill.x - aDest.x) / aRepeatSize.width) * aRepeatSize.width,
+                                 NSToIntFloor(float(aFill.y - aDest.y) / aRepeatSize.height) * aRepeatSize.height);
+  for (int32_t i = firstTilePos.x; i < aFill.XMost(); i += aRepeatSize.width) {
+    for (int32_t j = firstTilePos.y; j < aFill.YMost(); j += aRepeatSize.height) {
+      nsRect dest(i, j, aDest.width, aDest.height);
+      DrawResult result = DrawImageInternal(aContext, aPresContext, aImage, aGraphicsFilter,
+                                            dest, dest, aAnchor, aDirty, &svgContext,
+                                            aImageFlags, ExtendMode::CLAMP);
+      if (result != DrawResult::SUCCESS) {
+        return result;
+      }
+    }
+  }
+
+  return DrawResult::SUCCESS;
 }
 
 /* static */ DrawResult
 nsLayoutUtils::DrawImage(gfxContext&         aContext,
                          nsPresContext*      aPresContext,
                          imgIContainer*      aImage,
                          Filter              aGraphicsFilter,
                          const nsRect&       aDest,
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -1730,31 +1730,37 @@ public:
    *                            app units.
    *   @param aImage            The image.
    *   @param aImageSize        The unscaled size of the image being drawn.
    *                            (This might be the image's size if no scaling
    *                            occurs, or it might be the image's size if
    *                            the image is a vector image being rendered at
    *                            that size.)
    *   @param aDest             The position and scaled area where one copy of
-   *                            the image should be drawn.
+   *                            the image should be drawn. This area represents
+   *                            the image itself in its correct position as defined
+   *                            with the background-position css property.
    *   @param aFill             The area to be filled with copies of the image.
+   *   @param aRepeatSize       The distance between the positions of two subsequent
+   *                            repeats of the image. Sizes larger than aDest.Size()
+   *                            create gaps between the images.
    *   @param aAnchor           A point in aFill which we will ensure is
    *                            pixel-aligned in the output.
    *   @param aDirty            Pixels outside this area may be skipped.
    *   @param aImageFlags       Image flags of the imgIContainer::FLAG_* variety.
    *   @param aExtendMode       How to extend the image over the dest rect.
    */
   static DrawResult DrawBackgroundImage(gfxContext&         aContext,
                                         nsPresContext*      aPresContext,
                                         imgIContainer*      aImage,
                                         const CSSIntSize&   aImageSize,
                                         Filter              aGraphicsFilter,
                                         const nsRect&       aDest,
                                         const nsRect&       aFill,
+                                        const nsSize&       aRepeatSize,
                                         const nsPoint&      aAnchor,
                                         const nsRect&       aDirty,
                                         uint32_t            aImageFlags,
                                         ExtendMode          aExtendMode);
 
   /**
    * Draw an image.
    * See https://wiki.mozilla.org/Gecko:Image_Snapping_and_Rendering
--- a/layout/generic/nsGridContainerFrame.cpp
+++ b/layout/generic/nsGridContainerFrame.cpp
@@ -822,26 +822,26 @@ struct nsGridContainerFrame::TrackSizing
                (mMinSizingFunctions.Length() >= 1 &&
                 mRepeatAutoStart < mMinSizingFunctions.Length()));
   }
 
   /**
    * Initialize the number of auto-fill/fit tracks to use and return that.
    * (zero if no auto-fill/fit track was specified)
    */
-  uint32_t InitRepeatTracks(nscoord aGridGap, nscoord aMinSize, nscoord aSize,
-                            nscoord aMaxSize)
+  uint32_t InitRepeatTracks(const nsStyleCoord& aGridGap, nscoord aMinSize,
+                            nscoord aSize, nscoord aMaxSize)
   {
     uint32_t repeatTracks =
       CalculateRepeatFillCount(aGridGap, aMinSize, aSize, aMaxSize);
     SetNumRepeatTracks(repeatTracks);
     return repeatTracks;
   }
 
-  uint32_t CalculateRepeatFillCount(nscoord aGridGap,
+  uint32_t CalculateRepeatFillCount(const nsStyleCoord& aGridGap,
                                     nscoord aMinSize,
                                     nscoord aSize,
                                     nscoord aMaxSize) const
   {
     if (!mHasRepeatAuto) {
       return 0;
     }
     // Spec quotes are from https://drafts.csswg.org/css-grid/#repeat-notation
@@ -872,28 +872,30 @@ struct nsGridContainerFrame::TrackSizing
         // Use a minimum 1px for the repeat() track-size.
         if (trackSize < AppUnitsPerCSSPixel()) {
           trackSize = AppUnitsPerCSSPixel();
         }
         repeatTrackSize = trackSize;
       }
       sum += trackSize;
     }
+    nscoord gridGap =
+      std::max(nscoord(0), nsRuleNode::ComputeCoordPercentCalc(aGridGap, aSize));
     if (numTracks > 1) {
       // Add grid-gaps for all the tracks including the repeat() track.
-      sum += aGridGap * (numTracks - 1);
+      sum += gridGap * (numTracks - 1);
     }
     nscoord available = maxFill != NS_UNCONSTRAINEDSIZE ? maxFill : aMinSize;
     nscoord spaceToFill = available - sum;
     if (spaceToFill <= 0) {
       // "if any number of repetitions would overflow, then 1 repetition"
       return 1;
     }
     // Calculate the max number of tracks that fits without overflow.
-    uint32_t numRepeatTracks = (spaceToFill / (repeatTrackSize + aGridGap)) + 1;
+    uint32_t numRepeatTracks = (spaceToFill / (repeatTrackSize + gridGap)) + 1;
     if (maxFill == NS_UNCONSTRAINEDSIZE) {
       // "Otherwise, if the grid container has a definite min size in
       // the relevant axis, the number of repetitions is the largest possible
       // positive integer that fulfills that minimum requirement."
       ++numRepeatTracks; // one more to ensure the grid is at least min-size
     }
     // Clamp the number of repeat tracks so that the last line <= kMaxLine.
     // (note that |numTracks| already includes one repeat() track)
@@ -980,17 +982,17 @@ struct nsGridContainerFrame::TrackSizing
 /**
  * State for the tracks in one dimension.
  */
 struct nsGridContainerFrame::Tracks
 {
   explicit Tracks(LogicalAxis aAxis) : mAxis(aAxis) {}
 
   void Initialize(const TrackSizingFunctions& aFunctions,
-                  nscoord                     aGridGap,
+                  const nsStyleCoord&         aGridGap,
                   uint32_t                    aNumTracks,
                   nscoord                     aContentBoxSize);
 
   /**
    * Return true if aRange spans at least one track with an intrinsic sizing
    * function and does not span any tracks with a <flex> max-sizing function.
    * @param aRange the span of tracks to check
    * @param aConstraint if MIN_ISIZE, treat a <flex> min-sizing as 'min-content'
@@ -3155,32 +3157,32 @@ nsGridContainerFrame::Grid::PlaceGridIte
     auto finalRowRepeatCount = aState.mRowFunctions.NumRepeatTracks() - numEmptyRows;
     aState.mRowFunctions.SetNumRepeatTracks(finalRowRepeatCount);
   }
 }
 
 void
 nsGridContainerFrame::Tracks::Initialize(
   const TrackSizingFunctions& aFunctions,
-  nscoord                     aGridGap,
+  const nsStyleCoord&         aGridGap,
   uint32_t                    aNumTracks,
   nscoord                     aContentBoxSize)
 {
   MOZ_ASSERT(aNumTracks >= aFunctions.mExplicitGridOffset +
                              aFunctions.NumExplicitTracks());
   mSizes.SetLength(aNumTracks);
   PodZero(mSizes.Elements(), mSizes.Length());
   for (uint32_t i = 0, len = mSizes.Length(); i < len; ++i) {
     mSizes[i].Initialize(aContentBoxSize,
                          aFunctions.MinSizingFor(i),
                          aFunctions.MaxSizingFor(i));
   }
-  mGridGap = aGridGap;
+  auto gap = nsRuleNode::ComputeCoordPercentCalc(aGridGap, aContentBoxSize);
+  mGridGap = std::max(nscoord(0), gap);
   mContentBoxSize = aContentBoxSize;
-  MOZ_ASSERT(mGridGap >= nscoord(0), "negative grid gap");
 }
 
 /**
  * Return the [min|max]-content contribution of aChild to its parent (i.e.
  * the child's margin-box) in aAxis.
  */
 static nscoord
 ContentContribution(nsIFrame*                         aChild,
--- a/layout/inspector/tests/test_bug877690.html
+++ b/layout/inspector/tests/test_bug877690.html
@@ -88,17 +88,17 @@ function do_test() {
       "mediumorchid", "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen",
       "mediumturquoise", "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin",
       "navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered", "orchid", "palegoldenrod",
       "palegreen", "paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru", "pink", "plum",
       "powderblue", "purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown",
       "seagreen", "seashell", "sienna", "silver", "skyblue", "slateblue", "slategray", "slategrey",
       "snow", "springgreen", "steelblue", "tan", "teal", "thistle", "tomato", "transparent", "turquoise",
       "violet", "wheat", "white", "whitesmoke", "yellow", "yellowgreen", "no-repeat", "repeat",
-      "repeat-x", "repeat-y", "fixed", "scroll", "local", "center", "top", "bottom", "left", "right",
+      "repeat-x", "repeat-y", "space", "round", "fixed", "scroll", "local", "center", "top", "bottom", "left", "right",
       "border-box", "padding-box", "content-box", "border-box", "padding-box", "content-box", "text", "contain",
       "cover", "rgb", "hsl", "rgba", "hsla", "none", "-moz-element", "-moz-image-rect", "url", "linear-gradient",
       "radial-gradient", "repeating-linear-gradient", "repeating-radial-gradient", "-moz-linear-gradient",
       "-moz-radial-gradient", "-moz-repeating-linear-gradient", "-moz-repeating-radial-gradient" ];
   ok(testValues(values, expected), "Shorthand property values.");
 
   var prop = "border";
   var values = getCSSValuesForProperty(prop);
--- a/layout/printing/nsPrintEngine.cpp
+++ b/layout/printing/nsPrintEngine.cpp
@@ -1554,17 +1554,19 @@ nsPrintEngine::FirePrintingErrorEvent(ns
   event->SetTrusted(true);
 
   RefPtr<AsyncEventDispatcher> asyncDispatcher =
     new AsyncEventDispatcher(doc, event);
   asyncDispatcher->mOnlyChromeDispatch = true;
   asyncDispatcher->RunDOMEventWhenSafe();
 
   // Inform any progress listeners of the Error.
-  mPrt->DoOnStatusChange(aPrintError);
+  if (mPrt) {
+    mPrt->DoOnStatusChange(aPrintError);
+  }
 }
 
 //-----------------------------------------------------------------
 //-- Section: Reflow Methods
 //-----------------------------------------------------------------
 
 nsresult
 nsPrintEngine::ReconstructAndReflow(bool doSetPixelScale)
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1273154-2-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      body {
+        /* Force a large line-height so that we don't get a positioning
+           discrepancy on the test span depending on which font's metrics
+           are used in line-height computation. */
+        font: 64px/2 serif;
+      }
+      span {
+        font-family: "Mongolian Baiti", "Times New Roman", serif;
+        font-size: 32px;
+      }
+    </style>
+  </head>
+  <body>
+    <span>&#x202F;&#x1824;&#x1828;</span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1273154-2.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      body {
+        /* Force a large line-height so that we don't get a positioning
+           discrepancy on the test span depending on which font's metrics
+           are used in line-height computation. */
+        font: 64px/2 serif;
+      }
+      span {
+        /* Times does NOT support Mongolian characters, so the entire text
+           should be rendered as a single Mongolian Baiti run, including
+           the initial U+202F, even though it _is_ available in Times. */
+        font-family: "Times New Roman", "Mongolian Baiti", serif;
+        font-size: 32px;
+      }
+    </style>
+  </head>
+  <body>
+    <span>&#x202F;&#x1824;&#x1828;</span>
+  </body>
+</html>
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1949,9 +1949,10 @@ random-if(OSX==1006) == 1238243-2.html 1
 fuzzy(100,2000) == 1239564.html 1239564-ref.html
 == 1242172-1.html 1242172-1-ref.html
 == 1242172-2.html 1242172-2-ref.html
 == 1242781.html 1242781-ref.html
 == 1263845.html 1263845-ref.html
 == 1260543-1.html 1260543-1-ref.html
 == 1272997-1.html 1272997-1-ref.html
 random-if(!winWidget) == 1273154-1.html 1273154-1-ref.html # depends on Windows font
+random-if(!winWidget) == 1273154-2.html 1273154-2-ref.html # depends on Windows font
 == 1274368-1.html 1274368-1-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-grid/grid-column-gap-004-ref.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>Reference: Percentage 'grid-column-gap'</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1266268">
+  <style type="text/css">
+html,body {
+    color:black; background-color:white; font-size:16px; padding:0; margin:0;
+}
+separator { clear:both; display:block; height:6px; }
+
+.grid {
+  display: grid;
+  float: left;
+  position: relative;
+  border: 1px solid;
+  border-block-start: 2px solid blue;
+  grid-template: 3px 5px 7px / 11px 7px 5px;
+  padding: 1px 1px 3px 2px;
+  margin-right: 4px;
+  width: 60px;
+  height: 40px;
+  grid-column-gap: 6px;
+}
+.vl.grid, .vr.grid, .vlr.grid, .vrl.grid { grid-column-gap: 4px; }
+
+item1,item2,item3 {
+  display: block;
+  background: grey;
+  justify-self: stretch;
+  align-self: stretch;
+}
+
+item1 { grid-area: 1 / 1; }
+item2 { grid-area: 2 / 2; }
+item3 { grid-area: 3 / 3; }
+
+.hl  { writing-mode: horizontal-tb; direction:ltr; }
+.hr  { writing-mode: horizontal-tb; direction:rtl; }
+.vl  { writing-mode: vertical-lr; }
+.vr  { writing-mode: vertical-rl; }
+.vlr { writing-mode: vertical-lr; direction:rtl; }
+.vrl { writing-mode: vertical-rl; direction:ltr; }
+
+.astart { align-content:start; }
+.aend { align-content:end; }
+.aflexstart { align-content:flex-start; }
+.aflexend { align-content:flex-end; }
+.acenter { align-content:center; }
+.aleft { align-content:left; }
+.aright { align-content:right; }
+
+.aspace-between{ align-content:space-between; }
+.aspace-around { align-content:space-around;  }
+.aspace-evenly { align-content:space-evenly;  }
+
+.astretch1, .astretch2, .astretch3, .astretch4 { align-content:stretch; }
+.astretch2 { grid-template-rows: minmax(3px,auto) 5px 7px; }
+.astretch3 { grid-template-rows: minmax(3px,auto) minmax(5px,auto) 7px; }
+.astretch4 { grid-template-rows: minmax(3px,auto) minmax(5px,auto) minmax(7px,auto); }
+
+</style>
+</head>
+<body>
+
+<script>
+var gridwm = [ "hl", "hr", "vl", "vr", "vlr", "vrl" ];
+var test = [ "start", "end", "flexstart", "flexend", "center", "left", "right",
+             "space-between", "space-around", "space-evenly",
+             "stretch1", "stretch2", "stretch3", "stretch4" ];
+for (var k = 0; k < test.length; ++k) {
+  for (var i = 0; i < gridwm.length; ++i) {
+      var div = document.createElement("div");
+      div.className = "grid a" + test[k] + " " + gridwm[i];
+      div.appendChild(document.createElement("item1"));
+      div.appendChild(document.createElement("item2"));
+      div.appendChild(document.createElement("item3"));
+      document.body.appendChild(div)
+  }
+    document.body.appendChild(document.createElement("separator"));
+}
+</script>
+
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-grid/grid-column-gap-004.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>CSS Grid Test: Percentage 'grid-column-gap'</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1266268">
+  <link rel="help" href="http://dev.w3.org/csswg/css-grid/#gutters">
+  <link rel="match" href="grid-column-gap-004-ref.html">
+  <style type="text/css">
+html,body {
+    color:black; background-color:white; font-size:16px; padding:0; margin:0;
+}
+separator { clear:both; display:block; height:6px; }
+
+.grid {
+  display: grid;
+  float: left;
+  position: relative;
+  border: 1px solid;
+  border-block-start: 2px solid blue;
+  grid-template: 3px 5px 7px / 11px 7px 5px;
+  padding: 1px 1px 3px 2px;
+  margin-right: 4px;
+  width: 60px;
+  height: 40px;
+  grid-column-gap: 10%;
+}
+
+item1,item2,item3 {
+  display: block;
+  background: grey;
+  justify-self: stretch;
+  align-self: stretch;
+}
+
+item1 { grid-area: 1 / 1; }
+item2 { grid-area: 2 / 2; }
+item3 { grid-area: 3 / 3; }
+
+.hl  { writing-mode: horizontal-tb; direction:ltr; }
+.hr  { writing-mode: horizontal-tb; direction:rtl; }
+.vl  { writing-mode: vertical-lr; }
+.vr  { writing-mode: vertical-rl; }
+.vlr { writing-mode: vertical-lr; direction:rtl; }
+.vrl { writing-mode: vertical-rl; direction:ltr; }
+
+.astart { align-content:start; }
+.aend { align-content:end; }
+.aflexstart { align-content:flex-start; }
+.aflexend { align-content:flex-end; }
+.acenter { align-content:center; }
+.aleft { align-content:left; }
+.aright { align-content:right; }
+
+.aspace-between{ align-content:space-between; }
+.aspace-around { align-content:space-around;  }
+.aspace-evenly { align-content:space-evenly;  }
+
+.astretch1, .astretch2, .astretch3, .astretch4 { align-content:stretch; }
+.astretch2 { grid-template-rows: minmax(3px,auto) 5px 7px; }
+.astretch3 { grid-template-rows: minmax(3px,auto) minmax(5px,auto) 7px; }
+.astretch4 { grid-template-rows: minmax(3px,auto) minmax(5px,auto) minmax(7px,auto); }
+
+</style>
+</head>
+<body>
+
+<script>
+var gridwm = [ "hl", "hr", "vl", "vr", "vlr", "vrl" ];
+var test = [ "start", "end", "flexstart", "flexend", "center", "left", "right",
+             "space-between", "space-around", "space-evenly",
+             "stretch1", "stretch2", "stretch3", "stretch4" ];
+for (var k = 0; k < test.length; ++k) {
+  for (var i = 0; i < gridwm.length; ++i) {
+      var div = document.createElement("div");
+      div.className = "grid a" + test[k] + " " + gridwm[i];
+      div.appendChild(document.createElement("item1"));
+      div.appendChild(document.createElement("item2"));
+      div.appendChild(document.createElement("item3"));
+      document.body.appendChild(div)
+  }
+    document.body.appendChild(document.createElement("separator"));
+}
+</script>
+
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-grid/grid-row-gap-005-ref.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>Reference: Percentage 'grid-row-gap'</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1266268">
+  <style type="text/css">
+html,body {
+    color:black; background-color:white; font-size:16px; padding:0; margin:0;
+}
+separator { clear:both; display:block; height:6px; }
+
+.grid {
+  display: grid;
+  float: left;
+  position: relative;
+  border: 1px solid;
+  border-block-start: 2px solid blue;
+  grid-template: 3px 5px 7px / 11px 7px 5px;
+  padding: 1px 1px 3px 2px;
+  margin-right: 4px;
+  width: 40px;
+  height: 30px;
+  grid-row-gap: 3px;
+}
+.vl.grid, .vr.grid, .vlr.grid, .vrl.grid { grid-row-gap: 4px; }
+
+item1,item2,item3 {
+  display: block;
+  background: grey;
+  justify-self: stretch;
+  align-self: stretch;
+}
+
+item1 { grid-area: 1 / 1; }
+item2 { grid-area: 2 / 2; }
+item3 { grid-area: 3 / 3; }
+
+.hl  { writing-mode: horizontal-tb; direction:ltr; }
+.hr  { writing-mode: horizontal-tb; direction:rtl; }
+.vl  { writing-mode: vertical-lr; }
+.vr  { writing-mode: vertical-rl; }
+.vlr { writing-mode: vertical-lr; direction:rtl; }
+.vrl { writing-mode: vertical-rl; direction:ltr; }
+
+.astart { align-content:start; }
+.aend { align-content:end; }
+.aflexstart { align-content:flex-start; }
+.aflexend { align-content:flex-end; }
+.acenter { align-content:center; }
+.aleft { align-content:left; }
+.aright { align-content:right; }
+
+.aspace-between{ align-content:space-between; }
+.aspace-around { align-content:space-around;  }
+.aspace-evenly { align-content:space-evenly;  }
+
+.astretch1, .astretch2, .astretch3, .astretch4 { align-content:stretch; }
+.astretch2 { grid-template-rows: minmax(3px,auto) 5px 7px; }
+.astretch3 { grid-template-rows: minmax(3px,auto) minmax(5px,auto) 7px; }
+.astretch4 { grid-template-rows: minmax(3px,auto) minmax(5px,auto) minmax(7px,auto); }
+
+</style>
+</head>
+<body>
+
+<script>
+var gridwm = [ "hl", "hr", "vl", "vr", "vlr", "vrl" ];
+var test = [ "start", "end", "flexstart", "flexend", "center", "left", "right",
+             "space-between", "space-around", "space-evenly",
+             "stretch1", "stretch2", "stretch3", "stretch4" ];
+for (var k = 0; k < test.length; ++k) {
+  for (var i = 0; i < gridwm.length; ++i) {
+      var div = document.createElement("div");
+      div.className = "grid a" + test[k] + " " + gridwm[i];
+      div.appendChild(document.createElement("item1"));
+      div.appendChild(document.createElement("item2"));
+      div.appendChild(document.createElement("item3"));
+      document.body.appendChild(div)
+  }
+    document.body.appendChild(document.createElement("separator"));
+}
+</script>
+
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-grid/grid-row-gap-005.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>CSS Grid Test: Percentage 'grid-row-gap'</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1266268">
+  <link rel="help" href="http://dev.w3.org/csswg/css-grid/#gutters">
+  <link rel="match" href="grid-row-gap-005-ref.html">
+  <style type="text/css">
+html,body {
+    color:black; background-color:white; font-size:16px; padding:0; margin:0;
+}
+separator { clear:both; display:block; height:6px; }
+
+.grid {
+  display: grid;
+  float: left;
+  position: relative;
+  border: 1px solid;
+  border-block-start: 2px solid blue;
+  grid-template: 3px 5px 7px / 11px 7px 5px;
+  padding: 1px 1px 3px 2px;
+  margin-right: 4px;
+  width: 40px;
+  height: 30px;
+  grid-row-gap: 10%;
+}
+
+item1,item2,item3 {
+  display: block;
+  background: grey;
+  justify-self: stretch;
+  align-self: stretch;
+}
+
+item1 { grid-area: 1 / 1; }
+item2 { grid-area: 2 / 2; }
+item3 { grid-area: 3 / 3; }
+
+.hl  { writing-mode: horizontal-tb; direction:ltr; }
+.hr  { writing-mode: horizontal-tb; direction:rtl; }
+.vl  { writing-mode: vertical-lr; }
+.vr  { writing-mode: vertical-rl; }
+.vlr { writing-mode: vertical-lr; direction:rtl; }
+.vrl { writing-mode: vertical-rl; direction:ltr; }
+
+.astart { align-content:start; }
+.aend { align-content:end; }
+.aflexstart { align-content:flex-start; }
+.aflexend { align-content:flex-end; }
+.acenter { align-content:center; }
+.aleft { align-content:left; }
+.aright { align-content:right; }
+
+.aspace-between{ align-content:space-between; }
+.aspace-around { align-content:space-around;  }
+.aspace-evenly { align-content:space-evenly;  }
+
+.astretch1, .astretch2, .astretch3, .astretch4 { align-content:stretch; }
+.astretch2 { grid-template-rows: minmax(3px,auto) 5px 7px; }
+.astretch3 { grid-template-rows: minmax(3px,auto) minmax(5px,auto) 7px; }
+.astretch4 { grid-template-rows: minmax(3px,auto) minmax(5px,auto) minmax(7px,auto); }
+
+</style>
+</head>
+<body>
+
+<script>
+var gridwm = [ "hl", "hr", "vl", "vr", "vlr", "vrl" ];
+var test = [ "start", "end", "flexstart", "flexend", "center", "left", "right",
+             "space-between", "space-around", "space-evenly",
+             "stretch1", "stretch2", "stretch3", "stretch4" ];
+for (var k = 0; k < test.length; ++k) {
+  for (var i = 0; i < gridwm.length; ++i) {
+      var div = document.createElement("div");
+      div.className = "grid a" + test[k] + " " + gridwm[i];
+      div.appendChild(document.createElement("item1"));
+      div.appendChild(document.createElement("item2"));
+      div.appendChild(document.createElement("item3"));
+      document.body.appendChild(div)
+  }
+    document.body.appendChild(document.createElement("separator"));
+}
+</script>
+
+
+</body>
+</html>
--- a/layout/reftests/css-grid/reftest.list
+++ b/layout/reftests/css-grid/reftest.list
@@ -78,20 +78,22 @@ fuzzy-if(winWidget,1,36) == grid-auto-mi
 == grid-item-stretch-001.html grid-item-stretch-001-ref.html
 == grid-align-content-001.html grid-align-content-001-ref.html
 == grid-justify-content-001.html grid-justify-content-001-ref.html
 skip-if(Android&&isDebugBuild) == grid-justify-content-002.html grid-justify-content-002-ref.html # Bug 1245884 - slow
 skip-if(Android&&isDebugBuild) == grid-justify-content-003.html grid-justify-content-003-ref.html # Bug 1245884 - slow
 skip-if(Android&&isDebugBuild) == grid-column-gap-001.html grid-column-gap-001-ref.html # Bug 1245884 - slow
 == grid-column-gap-002.html grid-column-gap-002-ref.html
 == grid-column-gap-003.html grid-column-gap-003-ref.html
+== grid-column-gap-004.html grid-column-gap-004-ref.html
 == grid-row-gap-001.html grid-row-gap-001-ref.html
 skip-if(Android&&isDebugBuild) == grid-row-gap-002.html grid-row-gap-002-ref.html # Bug 1245884 - slow
 skip-if(Android&&isDebugBuild) == grid-row-gap-003.html grid-row-gap-003-ref.html # Bug 1245884 - slow
 skip-if(Android&&isDebugBuild) == grid-row-gap-004.html grid-row-gap-004-ref.html # Bug 1245884 - slow
+== grid-row-gap-005.html grid-row-gap-005-ref.html
 == grid-container-overflow-001.html grid-container-overflow-001-ref.html
 == grid-item-margin-left-auto-001.html grid-item-margin-left-auto-001-ref.html
 == grid-item-margin-left-auto-002.html grid-item-margin-left-auto-002-ref.html
 == grid-item-margin-left-auto-003.html grid-item-margin-left-auto-003-ref.html
 == grid-item-margin-left-auto-004.html grid-item-margin-left-auto-004-ref.html
 == grid-item-margin-left-right-auto-001.html grid-item-margin-left-right-auto-001-ref.html
 == grid-item-margin-left-right-auto-002.html grid-item-margin-left-right-auto-002-ref.html
 == grid-item-margin-left-right-auto-003.html grid-item-margin-left-right-auto-003-ref.html
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..42f8a2100b241aebf5c62e5057a51a933e6ff4d6
GIT binary patch
literal 156
zc%17D@N?(olHy`uVBq!ia0vp^3LwnH3?%tPCZz)@o&cW^*T{eW{{8L&@)Q*nxj)Kn
z2C}?8T^vIs!c9*t6g|MeapZyyCx5tG;>%xMaf|kGe7(+c_;Xn3)h_N?2`BbEYEqNf
jh)mQUWEGDofD?9o(@Z>PzdjKTG?u~B)z4*}Q$iB}(UCOa
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0289b0394131fd1e1ae6fec71a49e42522b9bfcd
GIT binary patch
literal 287
zc%17D@N?(olHy`uVBq!ia0vp^svyk41SGxwY!3laoCO|{#S9GG!XV7ZFl&wkP>{XE
z)7O>#0jn^pg-mGYlNzAVK2I0Nkch)?ub<^)G8ABa(3zpyWE<HWe)>Zex0q@~=Y)!Y
z7@@MwpFhVr6zDO(|75u5@{&J$IC9N)7i%u{T5-ehx!Rq5*Z&p#b?g7#*|PCn^dXHn
z_QTb+Ij{O}Nj|mXW&1ou;@IV6hVw11w%TH!WM<s|oalGHw{j0h+P9_|XS<Wx_?t3q
zMV5VPxUsAKXqb(~wa*ea7P%jKW-Dj*IB?^&<m9R5472wb-Z(DZ*6AT6s8!~@^3$yU
gIRW+c@7?!`pI1*ks?ZYz^eF>_r>mdKI;Vst0DnPpAOHXW
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-round-1-ref.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image repeat</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <style type="text/css">
+      .outer {
+        width: 72px;
+        height: 72px;
+        border: 1px solid black;
+        background-image: url(aqua-yellow-32x32.png);
+        background-size: 36px 36px;
+        background-repeat: repeat;
+      }
+    </style>
+    </head>
+    <body>
+      <div class="outer"></div>
+    </body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-round-1a.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image round</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://www.w3.org/TR/css3-background/#background-repeat">
+    <link rel="match" href="background-repeat-round-1-ref.html">
+    <meta name="assert" content="Test checks whether background-repeat: 'round' works correctly or not.">
+    <style type="text/css">
+      .outer {
+        width: 72px;
+        height: 72px;
+        border: 1px solid black;
+        background-image: url(aqua-yellow-32x32.png);
+        background-repeat: round;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-round-1b.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image round</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://www.w3.org/TR/css3-background/#background-repeat">
+    <link rel="match" href="background-repeat-round-1-ref.html">
+    <meta name="assert" content="Test checks whether background-repeat: 'round round' works correctly or not.">
+    <style type="text/css">
+      .outer {
+        width: 72px;
+        height: 72px;
+        border: 1px solid black;
+        background-image: url(aqua-yellow-32x32.png);
+        background-repeat: round round;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-round-1c.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image round</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://www.w3.org/TR/css3-background/#background-repeat">
+    <link rel="match" href="background-repeat-round-1-ref.html">
+    <meta name="assert" content="Test checks whether background-repeat: 'repeat round' works correctly or not.">
+    <style type="text/css">
+      .outer {
+        width: 72px;
+        height: 72px;
+        border: 1px solid black;
+        background-size: 36px 36px;
+        background-image: url(aqua-yellow-32x32.png);
+        background-repeat: repeat round;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-round-1d.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image round</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://www.w3.org/TR/css3-background/#background-repeat">
+    <link rel="match" href="background-repeat-round-1-ref.html">
+    <meta name="assert" content="Test checks whether background-repeat: 'round repeat' works correctly or not.">
+    <style type="text/css">
+      .outer {
+        width: 72px;
+        height: 72px;
+        border: 1px solid black;
+        background-size: 32px 36px;
+        background-image: url(aqua-yellow-32x32.png);
+        background-repeat: round repeat;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-round-1e.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image round</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://www.w3.org/TR/css3-background/#background-repeat">
+    <link rel="match" href="background-repeat-round-1-ref.html">
+    <meta name="assert" content="Test checks whether background-repeat: 'repeat round' works correctly or not.">
+    <style type="text/css">
+      .outer {
+        width: 72px;
+        height: 72px;
+        border: 1px solid black;
+        background-size: 36px 32px;
+        background-image: url(aqua-yellow-32x32.png);
+        background-repeat: repeat round;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-round-2-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image repeat</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <style type="text/css">
+    .outer {
+      width: 72px;
+      height: 72px;
+      border: 1px solid black;
+      background-image: url(aqua-yellow-32x32.png);
+      background-size: 36px 36px;
+      background-repeat: repeat-x;
+    }
+    </style>
+    </head>
+    <body>
+      <div class="outer"></div>
+    </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-round-2.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image round no-repeat</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://www.w3.org/TR/css3-background/#background-repeat">
+    <link rel="match" href="background-repeat-round-2-ref.html">
+    <meta name="assert" content="Test checks whether background-repeat: 'round no-repeat' works correctly or not.">
+    <style type="text/css">
+    .outer {
+      width: 72px;
+      height: 72px;
+      border: 1px solid black;
+      background-image: url(aqua-yellow-32x32.png);
+      background-repeat: round no-repeat;
+    }
+    </style>
+  </head>
+  <body>
+    <div class="outer"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-round-3-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image repeat</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <style type="text/css">
+      #outer {
+        width: 72px;
+        height: 72px;
+        border: 1px solid black;
+        background-image: url(aqua-yellow-32x32.png);
+        background-size: 36px 36px;
+        background-repeat: repeat-y;
+      }
+    </style>
+    </head>
+    <body>
+      <div id="outer"><div id=inner></div></div>
+    </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-round-3.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image no-repeat round</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://www.w3.org/TR/css3-background/#background-repeat">
+    <link rel="match" href="background-repeat-round-3-ref.html">
+    <meta name="assert" content="Test checks whether background-repeat: 'no-repeat round' works correctly or not.">
+    <style type="text/css">
+      .outer {
+        width: 72px;
+        height: 72px;
+        border: 1px solid black;
+        background-image: url(aqua-yellow-32x32.png);
+        background-size: 36px auto;
+        background-repeat: no-repeat round;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-round-4-ref.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image repeat with specified position</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <style type="text/css">
+    .outer {
+      width: 72px;
+      height: 72px;
+      border: 1px solid black;
+      background-image: url(aqua-yellow-32x32.png);
+      background-size: 36px 36px;
+      background-repeat: repeat;
+      background-position: 5px 5px;
+    }
+    </style>
+    </head>
+    <body>
+      <div class="outer"></div>
+    </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-round-4.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image round with specified position</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://www.w3.org/TR/css3-background/#background-repeat">
+    <link rel="match" href="background-repeat-round-4-ref.html">
+    <meta name="assert" content="Test checks whether background-repeat: 'round' works correctly or not.">
+    <style type="text/css">
+      #outer {
+        width: 72px;
+        height: 72px;
+        border: 1px solid black;
+        background-image: url(aqua-yellow-32x32.png);
+        background-repeat: round;
+        background-position: 5px 5px;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="outer"></div>
+  </body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-space-1-ref.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: position background image</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <style type="text/css">
+      .outer
+      {
+        border: 1px solid black;
+        width: 106px;
+        height: 106px;
+        display: flex;
+        justify-content: space-between;
+        align-content: space-between;
+        flex-wrap: wrap;
+      }
+      .inner
+      {
+        height: 32px;
+        width: 32px;
+        background-image: url(aqua-yellow-32x32.png);
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer">
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+    </div>
+  </body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-space-1a.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image space</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://www.w3.org/TR/css3-background/#background-repeat">
+    <link rel="match" href="background-repeat-space-1-ref.html">
+    <meta name="assert" content="Test checks whether background-repeat: 'space' works correctly or not.">
+    <style type="text/css">
+      .outer {
+        width: 106px;
+        height: 106px;
+        border: 1px solid black;
+        background-image: url(aqua-yellow-32x32.png);
+        background-repeat: space;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer"></div>
+  </body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-space-1b.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image space with position</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://www.w3.org/TR/css3-background/#background-repeat">
+    <link rel="match" href="background-repeat-space-1-ref.html">
+    <meta name="assert" content="Test checks whether background-repeat: 'space' works correctly or not.">
+    <style type="text/css">
+      .outer {
+        width: 106px;
+        height: 106px;
+        border: 1px solid black;
+        background-image: url(aqua-yellow-32x32.png);
+        background-position: 15px 15px;
+        background-repeat: space;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-space-1c.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image space</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://www.w3.org/TR/css3-background/#background-repeat">
+    <link rel="match" href="background-repeat-space-1-ref.html">
+    <meta name="assert" content="Test checks whether background-repeat: 'space space' works correctly or not.">
+    <style type="text/css">
+      .outer {
+        width: 106px;
+        height: 106px;
+        border: 1px solid black;
+        background-image: url(aqua-yellow-32x32.png);
+        background-repeat: space space;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer"></div>
+  </body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-space-2-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: position background image</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <style type="text/css">
+      .outer {
+        width: 48px;
+        height: 48px;
+        border: 1px solid black;
+        background-image: url(aqua-yellow-32x32.png);
+        background-repeat: no-repeat;
+        background-position: 5px 5px;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-space-2.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image round with specified position</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://www.w3.org/TR/css3-background/#background-repeat">
+    <link rel="match" href="background-repeat-round-4-ref.html">
+    <meta name="assert" content="Test checks whether background-repeat: 'space' works correctly or not.">
+    <style type="text/css">
+      .outer {
+        width: 48px;
+        height: 48px;
+        border: 1px solid black;
+        background-image: url(aqua-yellow-32x32.png);
+        background-repeat: space;
+        background-position: 5px 5px;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer"></div>
+  </body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-space-3-ref.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: position background image</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <style type="text/css">
+      .outer1
+      {
+        border: 1px solid black;
+        width: 106px;
+        height: 106px;
+        display: flex;
+        justify-content: space-between;
+      }
+      .inner1
+      {
+        height: 32px;
+        width: 32px;
+        background-image: url(aqua-yellow-32x32.png);
+        margin-top: 40px;
+      }
+      .outer2
+      {
+        border: 1px solid black;
+        width: 106px;
+        height: 106px;
+        display: flex;
+        align-content: space-between;
+        flex-wrap: wrap;
+      }
+      .inner2
+      {
+        height: 32px;
+        width: 32px;
+        background-image: url(aqua-yellow-32x32.png);
+        margin-left: 40px;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer1">
+      <div class="inner1"></div>
+      <div class="inner1"></div>
+      <div class="inner1"></div>
+    </div>
+    <div class="outer2">
+      <div class="inner2"></div>
+      <div class="inner2"></div>
+      <div class="inner2"></div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-space-3.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image round with specified position</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://www.w3.org/TR/css3-background/#background-repeat">
+    <link rel="match" href="background-repeat-round-3-ref.html">
+    <meta name="assert" content="Test checks whether background-repeat: 'no-repeat space' and 'space no-repeat' works correctly or not.">
+    <style type="text/css">
+      .outer {
+        width: 106px;
+        height: 106px;
+        border: 1px solid black;
+      }
+      .inner1 {
+        width: 106px;
+        height: 106px;
+        background-image: url(aqua-yellow-32x32.png);
+        background-repeat: space no-repeat;
+        background-position: 7px 40px;
+      }
+      .inner2 {
+        width: 106px;
+        height: 106px;
+        background-image: url(aqua-yellow-32x32.png);
+        background-repeat: no-repeat space;
+        background-position: 40px 7px;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer">
+      <div class="inner1"></div>
+    </div>
+    <div class="outer">
+      <div class="inner2"></div>
+    </div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-space-4-ref.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: position background image</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <style type="text/css">
+      .outer
+      {
+        border: 1px solid black;
+        width: 96px;
+        height: 106px;
+        display: flex;
+        align-content: space-between;
+        flex-wrap: wrap;
+      }
+      .inner
+      {
+        height: 32px;
+        width: 32px;
+        background-image: url(aqua-yellow-32x32.png);
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer">
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+    </div>
+  </body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-space-4.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image space</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://www.w3.org/TR/css3-background/#background-repeat">
+    <link rel="match" href="background-repeat-space-1-ref.html">
+    <meta name="assert" content="Test checks whether background-repeat: 'repeat space' works correctly or not.">
+    <style type="text/css">
+      .outer {
+        width: 96px;
+        height: 106px;
+        border: 1px solid black;
+        background-image: url(aqua-yellow-32x32.png);
+        background-repeat: repeat space;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer"></div>
+  </body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-space-5-ref.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: position background image</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <style type="text/css">
+      .outer
+      {
+        border: 1px solid black;
+        width: 106px;
+        height: 96px;
+        display: flex;
+        justify-content: space-between;
+        flex-wrap: wrap;
+      }
+      .inner
+      {
+        height: 32px;
+        width: 32px;
+        background-image: url(aqua-yellow-32x32.png);
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer">
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+    </div>
+  </body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-space-5.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image space</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://www.w3.org/TR/css3-background/#background-repeat">
+    <link rel="match" href="background-repeat-space-1-ref.html">
+    <meta name="assert" content="Test checks whether background-repeat: 'space repeat' works correctly or not.">
+    <style type="text/css">
+      .outer {
+        width: 106px;
+        height: 96px;
+        border: 1px solid black;
+        background-image: url(aqua-yellow-32x32.png);
+        background-repeat: space repeat;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer"></div>
+  </body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-space-6-ref.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: position background image</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <style type="text/css">
+      .outer
+      {
+        border: 1px solid black;
+        width: 192px;
+        height: 106px;
+        display: flex;
+        align-content: space-between;
+        flex-wrap: wrap;
+      }
+      .inner
+      {
+        height: 32px;
+        width: 64px;
+        background-image: url(aqua-yellow-32x32.png);
+        background-repeat: no-repeat;
+        background-size: 64px 32px;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer">
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+    </div>
+  </body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-space-6.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image space</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://www.w3.org/TR/css3-background/#background-repeat">
+    <link rel="match" href="background-repeat-space-1-ref.html">
+    <meta name="assert" content="Test checks whether background-repeat: 'round space' works correctly or not.">
+    <style type="text/css">
+      .outer {
+        width: 192px;
+        height: 106px;
+        border: 1px solid black;
+        background-size: 60px 32px;
+        background-image: url(aqua-yellow-32x32.png);
+        background-repeat: round space;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer"></div>
+  </body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-space-7-ref.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: position background image</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <style type="text/css">
+      .outer
+      {
+        border: 1px solid black;
+        width: 106px;
+        height: 192px;
+        display: flex;
+        justify-content: space-between;
+        flex-wrap: wrap;
+      }
+      .inner
+      {
+        height: 64px;
+        width: 32px;
+        background-image: url(aqua-yellow-32x32.png);
+        background-repeat: no-repeat;
+        background-size: 32px 64px;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer">
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+      <div class="inner"></div>
+    </div>
+  </body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-space-7.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image space</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://www.w3.org/TR/css3-background/#background-repeat">
+    <link rel="match" href="background-repeat-space-1-ref.html">
+    <meta name="assert" content="Test checks whether background-repeat: 'space round' works correctly or not.">
+    <style type="text/css">
+      .outer {
+        width: 106px;
+        height: 192px;
+        border: 1px solid black;
+        background-size: 32px 60px;
+        background-image: url(aqua-yellow-32x32.png);
+        background-repeat: space round;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer"></div>
+  </body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-space-8-ref.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: position background image</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <style type="text/css">
+      .outer
+      {
+        border: 20px solid rgba(0, 0, 0, 0.5);
+        width: 106px;
+        height: 106px;
+        background-image: url(aqua-yellow-37x37.png);
+        background-repeat: repeat;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer"></div>
+  </body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/background-repeat-space-8.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Background: background-repeat: background image space</title>
+    <link rel="author" title="Ethan Lin" href="mailto:ethlin@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://www.w3.org/TR/css3-background/#background-repeat">
+    <link rel="match" href="background-repeat-space-1-ref.html">
+    <meta name="assert" content="Test checks whether background-repeat: 'space' works correctly or not.">
+    <style type="text/css">
+      .outer {
+        width: 106px;
+        height: 106px;
+        border: 20px solid rgba(0, 0, 0, .5);
+        background-image: url(aqua-yellow-32x32.png);
+        background-repeat: space;
+    </style>
+  </head>
+  <body>
+    <div class="outer"></div>
+  </body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/background/reftest.list
@@ -0,0 +1,19 @@
+# mask-repeat round/space test cases
+== background-repeat-space-1a.html background-repeat-space-1-ref.html
+== background-repeat-space-1b.html background-repeat-space-1-ref.html
+== background-repeat-space-1c.html background-repeat-space-1-ref.html
+== background-repeat-space-2.html background-repeat-space-2-ref.html
+== background-repeat-space-3.html background-repeat-space-3-ref.html
+== background-repeat-space-4.html background-repeat-space-4-ref.html
+== background-repeat-space-5.html background-repeat-space-5-ref.html
+== background-repeat-space-6.html background-repeat-space-6-ref.html
+== background-repeat-space-7.html background-repeat-space-7-ref.html
+== background-repeat-space-8.html background-repeat-space-8-ref.html
+== background-repeat-round-1a.html background-repeat-round-1-ref.html
+== background-repeat-round-1b.html background-repeat-round-1-ref.html
+== background-repeat-round-1c.html background-repeat-round-1-ref.html
+== background-repeat-round-1d.html background-repeat-round-1-ref.html
+== background-repeat-round-1e.html background-repeat-round-1-ref.html
+== background-repeat-round-2.html background-repeat-round-2-ref.html
+== background-repeat-round-3.html background-repeat-round-3-ref.html
+== background-repeat-round-4.html background-repeat-round-4-ref.html
--- a/layout/style/nsCSSKeywordList.h
+++ b/layout/style/nsCSSKeywordList.h
@@ -783,9 +783,10 @@ CSS_KEY(reset-size, reset_size)
 //CSS_KEY(start, start)
 CSS_KEY(srgb, srgb)
 CSS_KEY(symbolic, symbolic)
 CSS_KEY(symbols, symbols)
 CSS_KEY(text-after-edge, text_after_edge)
 CSS_KEY(text-before-edge, text_before_edge)
 CSS_KEY(use-script, use_script)
 CSS_KEY(-moz-crisp-edges, _moz_crisp_edges)
+CSS_KEY(space, space)
 
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -2148,44 +2148,41 @@ CSSParserImpl::ParseSourceSizeList(const
     // Percentages are not allowed in a <source-size-value>, to avoid
     // confusion about what it would be relative to.
     if (ParseNonNegativeVariant(value, VARIANT_LCALC, nullptr) !=
         CSSParseResult::Ok) {
       hitError = true;
       break;
     }
 
+    if (GetToken(true)) {
+      if (!mToken.IsSymbol(',')) {
+        REPORT_UNEXPECTED_TOKEN(PEParseSourceSizeListNotComma);
+        hitError = true;
+        break;
+      }
+    }
+
     aQueries.AppendElement(query.forget());
     aValues.AppendElement(value);
-
-    if (!GetToken(true)) {
-      // Expected EOF
-      break;
-    }
-
-    if (eCSSToken_Symbol != mToken.mType || mToken.mSymbol != ',') {
-      REPORT_UNEXPECTED_TOKEN(PEParseSourceSizeListNotComma);
-      hitError = true;
-      break;
-    }
   }
 
   if (hitError) {
     // Per spec, a parse failure in this list invalidates it
     // entirely. Currently, this grammar is specified standalone and not part of
     // any larger grammar, so it doesn't make sense to try to advance the token
     // beyond it.
     OUTPUT_ERROR();
   }
 
   CLEAR_ERROR();
   ReleaseScanner();
   mHTMLMediaMode = false;
 
-  return !hitError;
+  return !aQueries.IsEmpty();
 }
 
 bool
 CSSParserImpl::ParseColorString(const nsSubstring& aBuffer,
                                 nsIURI* aURI, // for error reporting
                                 uint32_t aLineNumber, // for error reporting
                                 nsCSSValue& aValue,
                                 bool aSuppressErrors /* false */)
@@ -9750,22 +9747,22 @@ bool
 CSSParserImpl::ParseGridGap()
 {
   nsCSSValue first;
   if (ParseSingleTokenVariant(first, VARIANT_INHERIT, nullptr)) {
     AppendValue(eCSSProperty_grid_row_gap, first);
     AppendValue(eCSSProperty_grid_column_gap, first);
     return true;
   }
-  if (ParseNonNegativeVariant(first, VARIANT_LCALC, nullptr) !=
+  if (ParseNonNegativeVariant(first, VARIANT_LPCALC, nullptr) !=
         CSSParseResult::Ok) {
     return false;
   }
   nsCSSValue second;
-  auto result = ParseNonNegativeVariant(second, VARIANT_LCALC, nullptr);
+  auto result = ParseNonNegativeVariant(second, VARIANT_LPCALC, nullptr);
   if (result == CSSParseResult::Error) {
     return false;
   }
   AppendValue(eCSSProperty_grid_row_gap, first);
   AppendValue(eCSSProperty_grid_column_gap,
               result == CSSParseResult::NotFound ? first : second);
   return true;
 }
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -2138,20 +2138,20 @@ CSS_PROP_POSITION(
 CSS_PROP_POSITION(
     grid-column-gap,
     grid_column_gap,
     GridColumnGap,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_VALUE_NONNEGATIVE |
         CSS_PROPERTY_ENABLED_IN_UA_SHEETS,
     "layout.css.grid.enabled",
-    VARIANT_HL | VARIANT_CALC,
+    VARIANT_HLP | VARIANT_CALC,
     nullptr,
     offsetof(nsStylePosition, mGridColumnGap),
-    eStyleAnimType_nscoord)
+    eStyleAnimType_Coord)
 CSS_PROP_POSITION(
     grid-column-start,
     grid_column_start,
     GridColumnStart,
     CSS_PROPERTY_PARSE_FUNCTION,
     "layout.css.grid.enabled",
     0,
     nullptr,
@@ -2182,20 +2182,20 @@ CSS_PROP_POSITION(
 CSS_PROP_POSITION(
     grid-row-gap,
     grid_row_gap,
     GridRowGap,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_VALUE_NONNEGATIVE |
         CSS_PROPERTY_ENABLED_IN_UA_SHEETS,
     "layout.css.grid.enabled",
-    VARIANT_HL | VARIANT_CALC,
+    VARIANT_HLP | VARIANT_CALC,
     nullptr,
     offsetof(nsStylePosition, mGridRowGap),
-    eStyleAnimType_nscoord)
+    eStyleAnimType_Coord)
 CSS_PROP_POSITION(
     grid-row-start,
     grid_row_start,
     GridRowStart,
     CSS_PROPERTY_PARSE_FUNCTION,
     "layout.css.grid.enabled",
     0,
     nullptr,
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -930,22 +930,26 @@ const KTableEntry nsCSSProps::kImageLaye
   { eCSSKeyword_UNKNOWN, -1 }
 };
 
 const KTableEntry nsCSSProps::kImageLayerRepeatKTable[] = {
   { eCSSKeyword_no_repeat,  NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT },
   { eCSSKeyword_repeat,     NS_STYLE_IMAGELAYER_REPEAT_REPEAT },
   { eCSSKeyword_repeat_x,   NS_STYLE_IMAGELAYER_REPEAT_REPEAT_X },
   { eCSSKeyword_repeat_y,   NS_STYLE_IMAGELAYER_REPEAT_REPEAT_Y },
+  { eCSSKeyword_round,      NS_STYLE_IMAGELAYER_REPEAT_ROUND},
+  { eCSSKeyword_space,      NS_STYLE_IMAGELAYER_REPEAT_SPACE},
   { eCSSKeyword_UNKNOWN, -1 }
 };
 
 const KTableEntry nsCSSProps::kImageLayerRepeatPartKTable[] = {
   { eCSSKeyword_no_repeat,  NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT },
   { eCSSKeyword_repeat,     NS_STYLE_IMAGELAYER_REPEAT_REPEAT },
+  { eCSSKeyword_round,      NS_STYLE_IMAGELAYER_REPEAT_ROUND},
+  { eCSSKeyword_space,      NS_STYLE_IMAGELAYER_REPEAT_SPACE},
   { eCSSKeyword_UNKNOWN, -1 }
 };
 
 const KTableEntry nsCSSProps::kImageLayerSizeKTable[] = {
   { eCSSKeyword_contain, NS_STYLE_IMAGELAYER_SIZE_CONTAIN },
   { eCSSKeyword_cover,   NS_STYLE_IMAGELAYER_SIZE_COVER },
   { eCSSKeyword_UNKNOWN, -1 }
 };
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -2831,25 +2831,25 @@ nsComputedDOMStyle::DoGetGridRowEnd()
 {
   return GetGridLine(StylePosition()->mGridRowEnd);
 }
 
 already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetGridColumnGap()
 {
   RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
-  val->SetAppUnits(StylePosition()->mGridColumnGap);
+  SetValueToCoord(val, StylePosition()->mGridColumnGap, true);
   return val.forget();
 }
 
 already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetGridRowGap()
 {
   RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
-  val->SetAppUnits(StylePosition()->mGridRowGap);
+  SetValueToCoord(val, StylePosition()->mGridRowGap, true);
   return val.forget();
 }
 
 already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetPaddingTop()
 {
   return GetPaddingWidthFor(NS_SIDE_TOP);
 }
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -6515,16 +6515,20 @@ struct BackgroundItemComputer<nsCSSValue
       aComputedValue.mXRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
       aComputedValue.mYRepeat = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
       break;
     case NS_STYLE_IMAGELAYER_REPEAT_REPEAT_Y:
       aComputedValue.mXRepeat = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
       aComputedValue.mYRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
       break;
     default:
+      NS_ASSERTION(value == NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT ||
+                   value == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
+                   value == NS_STYLE_IMAGELAYER_REPEAT_SPACE ||
+                   value == NS_STYLE_IMAGELAYER_REPEAT_ROUND, "Unexpected value");
       aComputedValue.mXRepeat = value;
       hasContraction = false;
       break;
     }
 
     if (hasContraction) {
       NS_ASSERTION(aSpecifiedValue->mYValue.GetUnit() == eCSSUnit_Null,
                    "Invalid unit.");
@@ -6533,17 +6537,19 @@ struct BackgroundItemComputer<nsCSSValue
 
     switch (aSpecifiedValue->mYValue.GetUnit()) {
     case eCSSUnit_Null:
       aComputedValue.mYRepeat = aComputedValue.mXRepeat;
       break;
     case eCSSUnit_Enumerated:
       value = aSpecifiedValue->mYValue.GetIntValue();
       NS_ASSERTION(value == NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT ||
-                   value == NS_STYLE_IMAGELAYER_REPEAT_REPEAT, "Unexpected value");
+                   value == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
+                   value == NS_STYLE_IMAGELAYER_REPEAT_SPACE ||
+                   value == NS_STYLE_IMAGELAYER_REPEAT_ROUND, "Unexpected value");
       aComputedValue.mYRepeat = value;
       break;
     default:
       NS_NOTREACHED("Unexpected CSS value");
       break;
     }
   }
 };
@@ -8578,37 +8584,32 @@ nsRuleNode::ComputePositionData(void* aS
 
   // grid-row-end
   SetGridLine(*aRuleData->ValueForGridRowEnd(),
               pos->mGridRowEnd,
               parentPos->mGridRowEnd,
               conditions);
 
   // grid-column-gap
-  nsStyleCoord tempCoord;
   if (SetCoord(*aRuleData->ValueForGridColumnGap(),
-               tempCoord, nsStyleCoord(parentPos->mGridColumnGap,
-                                       nsStyleCoord::CoordConstructor),
-               SETCOORD_LH | SETCOORD_INITIAL_ZERO | SETCOORD_CALC_LENGTH_ONLY |
+               pos->mGridColumnGap, parentPos->mGridColumnGap,
+               SETCOORD_LPH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC |
                SETCOORD_CALC_CLAMP_NONNEGATIVE | SETCOORD_UNSET_INITIAL,
                aContext, mPresContext, conditions)) {
-    pos->mGridColumnGap = tempCoord.GetCoordValue();
   } else {
     MOZ_ASSERT(aRuleData->ValueForGridColumnGap()->GetUnit() == eCSSUnit_Null,
                "unexpected unit");
   }
 
   // grid-row-gap
   if (SetCoord(*aRuleData->ValueForGridRowGap(),
-               tempCoord, nsStyleCoord(parentPos->mGridRowGap,
-                                       nsStyleCoord::CoordConstructor),
-               SETCOORD_LH | SETCOORD_INITIAL_ZERO | SETCOORD_CALC_LENGTH_ONLY |
+               pos->mGridRowGap, parentPos->mGridRowGap,
+               SETCOORD_LPH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC |
                SETCOORD_CALC_CLAMP_NONNEGATIVE | SETCOORD_UNSET_INITIAL,
                aContext, mPresContext, conditions)) {
-    pos->mGridRowGap = tempCoord.GetCoordValue();
   } else {
     MOZ_ASSERT(aRuleData->ValueForGridRowGap()->GetUnit() == eCSSUnit_Null,
                "unexpected unit");
   }
 
   // z-index
   const nsCSSValue* zIndexValue = aRuleData->ValueForZIndex();
   if (! SetCoord(*zIndexValue, pos->mZIndex, parentPos->mZIndex,
--- a/layout/style/nsStyleConsts.h
+++ b/layout/style/nsStyleConsts.h
@@ -295,16 +295,18 @@ enum class FillMode : uint32_t;
 #define NS_STYLE_IMAGELAYER_POSITION_LEFT            (1<<3)
 #define NS_STYLE_IMAGELAYER_POSITION_RIGHT           (1<<4)
 
 // See nsStyleImageLayers
 #define NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT         0x00
 #define NS_STYLE_IMAGELAYER_REPEAT_REPEAT_X          0x01
 #define NS_STYLE_IMAGELAYER_REPEAT_REPEAT_Y          0x02
 #define NS_STYLE_IMAGELAYER_REPEAT_REPEAT            0x03
+#define NS_STYLE_IMAGELAYER_REPEAT_SPACE             0x04
+#define NS_STYLE_IMAGELAYER_REPEAT_ROUND             0x05
 
 // See nsStyleImageLayers
 #define NS_STYLE_IMAGELAYER_SIZE_CONTAIN             0
 #define NS_STYLE_IMAGELAYER_SIZE_COVER               1
 
 // Mask mode
 #define NS_STYLE_MASK_MODE_ALPHA                0
 #define NS_STYLE_MASK_MODE_LUMINANCE            1
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1417,16 +1417,18 @@ nsStylePosition::nsStylePosition(StyleSt
   , mJustifySelf(NS_STYLE_JUSTIFY_AUTO)
   , mFlexDirection(NS_STYLE_FLEX_DIRECTION_ROW)
   , mFlexWrap(NS_STYLE_FLEX_WRAP_NOWRAP)
   , mObjectFit(NS_STYLE_OBJECT_FIT_FILL)
   , mOrder(NS_STYLE_ORDER_INITIAL)
   , mFlexGrow(0.0f)
   , mFlexShrink(1.0f)
   , mZIndex(eStyleUnit_Auto)
+  , mGridColumnGap(nscoord(0), nsStyleCoord::CoordConstructor)
+  , mGridRowGap(nscoord(0), nsStyleCoord::CoordConstructor)
 {
   MOZ_COUNT_CTOR(nsStylePosition);
 
   // positioning values not inherited
 
   mObjectPosition.SetInitialPercentValues(0.5f);
 
   nsStyleCoord  autoCoord(eStyleUnit_Auto);
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -1720,18 +1720,18 @@ public:
 
   // nullptr for 'none'
   RefPtr<mozilla::css::GridTemplateAreasValue> mGridTemplateAreas;
 
   nsStyleGridLine mGridColumnStart;
   nsStyleGridLine mGridColumnEnd;
   nsStyleGridLine mGridRowStart;
   nsStyleGridLine mGridRowEnd;
-  nscoord         mGridColumnGap;       // [reset] coord, calc
-  nscoord         mGridRowGap;          // [reset] coord, calc
+  nsStyleCoord    mGridColumnGap;       // [reset] coord, percent, calc
+  nsStyleCoord    mGridRowGap;          // [reset] coord, percent, calc
 
   // FIXME: Logical-coordinate equivalents to these WidthDepends... and
   // HeightDepends... methods have been introduced (see below); we probably
   // want to work towards removing the physical methods, and using the logical
   // ones in all cases.
 
   bool WidthDependsOnContainer() const
     {
--- a/layout/style/test/gen-css-properties.py
+++ b/layout/style/test/gen-css-properties.py
@@ -7,15 +7,18 @@ from __future__ import print_function
 import os
 import sys
 import subprocess
 
 def main(output, css_properties, exe):
     # moz.build passes in the exe name without any path, so to run it we need to
     # prepend the './'
     run_exe = exe if os.path.isabs(exe) else './%s' % exe
-    data = subprocess.check_output([run_exe])
+
+    # Use universal_newlines so everything is '\n', which gets converted to
+    # '\r\n' when writing out the file in Windows.
+    data = subprocess.check_output([run_exe], universal_newlines=True)
     with open(css_properties) as f:
         data += f.read()
     output.write(data)
 
 if __name__ == '__main__':
     main(sys.stdout, *sys.argv[1:])
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -2399,23 +2399,42 @@ var gCSSProperties = {
       "repeat-x, repeat-x",
       "repeat, no-repeat",
       "repeat-y, no-repeat, repeat-y",
       "repeat, repeat, repeat",
       "repeat no-repeat",
       "no-repeat repeat",
       "no-repeat no-repeat",
       "repeat repeat, repeat repeat",
+      "round, repeat",
+      "round repeat, repeat-x",
+      "round no-repeat, repeat-y",
+      "round round",
+      "space, repeat",
+      "space repeat, repeat-x",
+      "space no-repeat, repeat-y",
+      "space space",
+      "space round"
     ],
     invalid_values: [ "repeat repeat repeat",
                       "repeat-x repeat-y",
                       "repeat repeat-x",
                       "repeat repeat-y",
                       "repeat-x repeat",
-                      "repeat-y repeat" ]
+                      "repeat-y repeat",
+                      "round round round",
+                      "repeat-x round",
+                      "round repeat-x",
+                      "repeat-y round",
+                      "round repeat-y",
+                      "space space space",
+                      "repeat-x space",
+                      "space repeat-x",
+                      "repeat-y space",
+                      "space repeat-y" ]
   },
   "background-size": {
     domProp: "backgroundSize",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "auto", "auto auto" ],
     other_values: [ "contain", "cover", "100px auto", "auto 100px", "100% auto", "auto 100%", "25% 50px", "3em 40%",
       "calc(20px)",
@@ -6438,36 +6457,38 @@ if (IsCSSPropertyPrefEnabled("layout.css
     invalid_values: gridAreaInvalidValues
   };
 
   gCSSProperties["grid-column-gap"] = {
     domProp: "gridColumnGap",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "0" ],
-    other_values: [ "2px", "1em", "calc(1px + 1em)" ],
-    invalid_values: [ "-1px", "2%", "auto", "none", "1px 1px", "calc(1%)" ],
+    other_values: [ "2px", "2%", "1em", "calc(1px + 1em)", "calc(1%)",
+                    "calc(1% + 1ch)" ],
+    invalid_values: [ "-1px", "auto", "none", "1px 1px", "-1%" ],
   };
   gCSSProperties["grid-row-gap"] = {
     domProp: "gridRowGap",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "0" ],
-    other_values: [ "2px", "1em", "calc(1px + 1em)" ],
-    invalid_values: [ "-1px", "2%", "auto", "none", "1px 1px", "calc(1%)" ],
+    other_values: [ "2px", "2%", "1em", "calc(1px + 1em)", "calc(1%)",
+                    "calc(1% + 1ch)" ],
+    invalid_values: [ "-1px", "auto", "none", "1px 1px", "-1%" ],
   };
   gCSSProperties["grid-gap"] = {
     domProp: "gridGap",
     inherited: false,
     type: CSS_TYPE_TRUE_SHORTHAND,
     subproperties: [ "grid-column-gap", "grid-row-gap" ],
     initial_values: [ "0", "0 0" ],
-    other_values: [ "1ch 0", "1em 1px", "calc(1px + 1ch)" ],
+    other_values: [ "1ch 0", "1px 1%", "1em 1px", "calc(1px) calc(1%)" ],
     invalid_values: [ "-1px", "1px -1px", "1px 1px 1px", "inherit 1px",
-                      "1px 1%", "1px auto" ]
+                      "1px auto" ]
   };
 }
 
 if (IsCSSPropertyPrefEnabled("layout.css.display-contents.enabled")) {
   gCSSProperties["display"].other_values.push("contents");
 }
 
 if (IsCSSPropertyPrefEnabled("layout.css.contain.enabled")) {
--- a/layout/style/test/test_transitions_per_property.html
+++ b/layout/style/test/test_transitions_per_property.html
@@ -1892,16 +1892,18 @@ function test_font_weight(prop) {
 }
 
 function test_grid_gap(prop) {
   if (!SpecialPowers.getBoolPref("layout.css.grid.enabled")) {
     return;
   }
   test_length_transition(prop);
   test_length_clamped(prop);
+  test_percent_transition(prop);
+  test_percent_clamped(prop);
 }
 
 function test_pos_integer_or_auto_transition(prop) {
   div.style.setProperty("transition-property", "none", "");
   div.style.setProperty(prop, "4", "");
   is(cs.getPropertyValue(prop), "4",
      "integer-valued property " + prop + ": computed value before transition");
   div.style.setProperty("transition-property", prop, "");
--- a/layout/tools/reftest/runreftest.py
+++ b/layout/tools/reftest/runreftest.py
@@ -499,17 +499,17 @@ class RefTest(object):
 
         return int(any(t.retcode != 0 for t in threads))
 
     def handleTimeout(self, timeout, proc, utilityPath, debuggerInfo):
         """handle process output timeout"""
         # TODO: bug 913975 : _processOutput should call self.processOutputLine
         # one more time one timeout (I think)
         self.log.error("%s | application timed out after %d seconds with no output" % (self.lastTestSeen, int(timeout)))
-        self.log.warning("Force-terminating active process(es).");
+        self.log.error("Force-terminating active process(es).");
         self.killAndGetStack(
             proc, utilityPath, debuggerInfo, dump_screen=not debuggerInfo)
 
     def dumpScreen(self, utilityPath):
         if self.haveDumpedScreen:
             self.log.info("Not taking screenshot here: see the one that was previously logged")
             return
         self.haveDumpedScreen = True
--- a/layout/tools/reftest/runreftestmulet.py
+++ b/layout/tools/reftest/runreftestmulet.py
@@ -157,17 +157,17 @@ class MuletReftest(RefTest):
 
         if self.build_type == "mulet":
             args += ['-chrome', 'chrome://b2g/content/shell.html']
         return cmd, args
 
     def _on_timeout(self):
         msg = "%s | application timed out after %s seconds with no output"
         self.log.testFail(msg % (self.last_test, self.timeout))
-        self.log.warning("Force-terminating active process(es).");
+        self.log.error("Force-terminating active process(es).");
 
         # kill process to get a stack
         self.runner.stop(sig=signal.SIGABRT)
 
     def _unlockScreen(self):
         self.marionette.set_context(self.marionette.CONTEXT_CONTENT)
         self.marionette.import_script(os.path.abspath(
             os.path.join(__file__, os.path.pardir, "gaia_lock_screen.js")))
--- a/mobile/android/app/build.gradle
+++ b/mobile/android/app/build.gradle
@@ -1,17 +1,17 @@
 buildDir "${topobjdir}/gradle/build/mobile/android/app"
 
 apply plugin: 'android-sdk-manager' // Must come before 'com.android.*'.
 apply plugin: 'com.android.application'
 apply plugin: 'checkstyle'
 
 android {
     compileSdkVersion 23
-    buildToolsVersion "23.0.1"
+    buildToolsVersion mozconfig.substs.ANDROID_BUILD_TOOLS_VERSION
 
     defaultConfig {
         targetSdkVersion 23
         minSdkVersion 15 
         applicationId mozconfig.substs.ANDROID_PACKAGE_NAME
         testApplicationId 'org.mozilla.roboexample.test'
         testInstrumentationRunner 'org.mozilla.gecko.FennecInstrumentationTestRunner'
         manifestPlaceholders = [
@@ -166,40 +166,40 @@ android {
             // we have tests that start test servers and the bound ports
             // collide.  We'll fix this soon to have much faster test cycles.
             maxParallelForks 1
         }
     }
 }
 
 dependencies {
-    compile 'com.android.support:support-v4:23.0.1'
-    compile 'com.android.support:appcompat-v7:23.0.1'
-    compile 'com.android.support:cardview-v7:23.0.1'
-    compile 'com.android.support:recyclerview-v7:23.0.1'
-    compile 'com.android.support:design:23.0.1'
+    compile "com.android.support:support-v4:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
+    compile "com.android.support:appcompat-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
+    compile "com.android.support:cardview-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
+    compile "com.android.support:recyclerview-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
+    compile "com.android.support:design:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
 
     if (mozconfig.substs.MOZ_NATIVE_DEVICES) {
-        compile 'com.android.support:mediarouter-v7:23.0.1'
-        compile 'com.google.android.gms:play-services-basement:8.1.0'
-        compile 'com.google.android.gms:play-services-base:8.1.0'
-        compile 'com.google.android.gms:play-services-cast:8.1.0'
+        compile "com.android.support:mediarouter-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
+        compile "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
+        compile "com.google.android.gms:play-services-base:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
+        compile "com.google.android.gms:play-services-cast:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
     }
 
     if (mozconfig.substs.MOZ_INSTALL_TRACKING) {
-        compile 'com.google.android.gms:play-services-ads:8.1.0'
-        compile 'com.google.android.gms:play-services-analytics:8.1.0'
-        compile 'com.google.android.gms:play-services-appindexing:8.1.0'
-        compile 'com.google.android.gms:play-services-basement:8.1.0'
+        compile "com.google.android.gms:play-services-ads:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
+        compile "com.google.android.gms:play-services-analytics:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
+        compile "com.google.android.gms:play-services-appindexing:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
+        compile "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
     }
 
     if (mozconfig.substs.MOZ_ANDROID_GCM) {
-        compile 'com.google.android.gms:play-services-basement:8.1.0'
-        compile 'com.google.android.gms:play-services-base:8.1.0'
-        compile 'com.google.android.gms:play-services-gcm:8.1.0'
+        compile "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
+        compile "com.google.android.gms:play-services-base:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
+        compile "com.google.android.gms:play-services-gcm:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
     }
 
     // Gradle based builds include LeakCanary.  Gradle based tests include the no-op version.  Mach
     // based builds only include the no-op version of this library.
     compile 'com.squareup.leakcanary:leakcanary-android:1.4-beta1'
     testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
 
     compile project(':thirdparty')
--- a/mobile/android/bouncer/build.gradle
+++ b/mobile/android/bouncer/build.gradle
@@ -1,15 +1,15 @@
 buildDir "${topobjdir}/gradle/build/mobile/android/bouncer"
 
 apply plugin: 'com.android.application'
 
 android {
     compileSdkVersion 23
-    buildToolsVersion "23.0.1"
+    buildToolsVersion mozconfig.substs.ANDROID_BUILD_TOOLS_VERSION
 
     defaultConfig {
         targetSdkVersion 23
         minSdkVersion 15 
         applicationId mozconfig.substs.ANDROID_PACKAGE_NAME
     }
 
     compileOptions {
--- a/mobile/android/thirdparty/build.gradle
+++ b/mobile/android/thirdparty/build.gradle
@@ -1,15 +1,15 @@
 buildDir "${topobjdir}/gradle/build/mobile/android/thirdparty"
 
 apply plugin: 'com.android.library'
 
 android {
     compileSdkVersion 23
-    buildToolsVersion "23.0.1"
+    buildToolsVersion mozconfig.substs.ANDROID_BUILD_TOOLS_VERSION
 
     defaultConfig {
         targetSdkVersion 23
         minSdkVersion 15
     }
 
     compileOptions {
         sourceCompatibility JavaVersion.VERSION_1_7
@@ -33,17 +33,17 @@ android {
                 // here is only the no-op library for mach-based builds.
                 exclude 'com/squareup/leakcanary/**'
             }
         }
     }
 }
 
 dependencies {
-    compile 'com.android.support:support-v4:23.0.1'
+    compile "com.android.support:support-v4:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
 }
 
 apply plugin: 'idea'
 
 idea {
     module {
         // This is cosmetic.  See the excludes in the root project.
         if (!mozconfig.substs.MOZ_INSTALL_TRACKING) {
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1513,17 +1513,17 @@ pref("network.http.enforce-framing.soft"
 pref("network.http.enable-packaged-apps", false);
 
 // Enable this to bring in the signature verification if the signature exists.
 // Set to false if you don't need the signed packaged web app support (i.e. NSec).
 pref("network.http.signed-packages.enabled", false);
 
 // If it is set to false, headers with empty value will not appear in the header
 // array - behavior as it used to be. If it is true: empty headers coming from
-// the network will exits in header array as empty string. Call SetHeader with
+// the network will exist in header array as empty string. Call SetHeader with
 // an empty value will still delete the header.(Bug 6699259)
 pref("network.http.keep_empty_response_headers_as_empty_string", false);
 
 // default values for FTP
 // in a DSCP environment this should be 40 (0x28, or AF11), per RFC-4594,
 // Section 4.8 "High-Throughput Data Service Class", and 80 (0x50, or AF22)
 // per Section 4.7 "Low-Latency Data Service Class".
 pref("network.ftp.data.qos", 0);
--- a/netwerk/base/LoadInfo.cpp
+++ b/netwerk/base/LoadInfo.cpp
@@ -460,16 +460,25 @@ NS_IMETHODIMP
 LoadInfo::GetAllowChrome(bool* aResult)
 {
   *aResult =
     (mSecurityFlags & nsILoadInfo::SEC_ALLOW_CHROME);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+LoadInfo::GetDisallowScript(bool* aResult)
+{
+  *aResult =
+    (mSecurityFlags & nsILoadInfo::SEC_DISALLOW_SCRIPT);
+  return NS_OK;
+}
+
+
+NS_IMETHODIMP
 LoadInfo::GetDontFollowRedirects(bool* aResult)
 {
   *aResult =
     (mSecurityFlags & nsILoadInfo::SEC_DONT_FOLLOW_REDIRECTS);
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/netwerk/base/nsILoadInfo.idl
+++ b/netwerk/base/nsILoadInfo.idl
@@ -143,44 +143,49 @@ interface nsILoadInfo : nsISupports
   const unsigned long SEC_ABOUT_BLANK_INHERITS = (1<<9);
 
   /**
    * Allow access to chrome: packages that are content accessible.
    */
   const unsigned long SEC_ALLOW_CHROME = (1<<10);
 
   /**
+   * Disallow access to javascript: uris.
+   */
+  const unsigned long SEC_DISALLOW_SCRIPT = (1<<11);
+
+  /**
    * Don't follow redirects. Instead the redirect response is returned
    * as a successful response for the channel.
    *
    * Redirects not initiated by a server response, i.e. REDIRECT_INTERNAL and
    * REDIRECT_STS_UPGRADE, are still followed.
    *
    * Note: If this flag is set and the channel response is a redirect, then
    * the response body might not be available.
    * This can happen if the redirect was cached.
    */
-  const unsigned long SEC_DONT_FOLLOW_REDIRECTS = (1<<11);
+  const unsigned long SEC_DONT_FOLLOW_REDIRECTS = (1<<12);
 
   /**
    * Force private browsing. Setting this flag the private browsing can be
    * enforce even when a loading is not happening in the context of a document.
    *
    * If the flag is true, even if a document context is present,
    * GetUsePrivateBrowsing will always return true.
    */
-  const unsigned long SEC_FORCE_PRIVATE_BROWSING = (1<<12);
+  const unsigned long SEC_FORCE_PRIVATE_BROWSING = (1<<13);
 
   /**
    * The SEC_FORCE_INHERIT_PRINCIPAL flag may be dropped when a load info
    * object is created.  Specifically, it will be dropped if the SEC_SANDBOXED
    * flag is also present.  This flag is set if SEC_FORCE_INHERIT_PRINCIPAL was
    * dropped.
    */
-  const unsigned long SEC_FORCE_INHERIT_PRINCIPAL_WAS_DROPPED = (1<<13);
+  const unsigned long SEC_FORCE_INHERIT_PRINCIPAL_WAS_DROPPED = (1<<14);
 
   /**
    * The loadingPrincipal is the principal that is responsible for the load.
    * It is *NOT* the principal tied to the resource/URI that this
    * channel is loading, it's the principal of the resource's
    * caller or requester. For example, if this channel is loading
    * an image from http://b.com that is embedded in a document
    * who's origin is http://a.com, the loadingPrincipal is http://a.com.
@@ -309,16 +314,22 @@ interface nsILoadInfo : nsISupports
 
   /**
    * If allowChrome is true, then use nsIScriptSecurityManager::ALLOW_CHROME
    * when calling CheckLoadURIWithPrincipal().
    */
   [infallible] readonly attribute boolean allowChrome;
 
   /**
+   * If disallowScript is true, then use nsIScriptSecurityManager::DISALLOW_SCRIPT
+   * when calling CheckLoadURIWithPrincipal().
+   */
+  [infallible] readonly attribute boolean disallowScript;
+
+  /**
    * Returns true if SEC_DONT_FOLLOW_REDIRECTS is set.
    */
   [infallible] readonly attribute boolean dontFollowRedirects;
 
   /**
    * The external contentPolicyType of the channel, used for security checks
    * like Mixed Content Blocking and Content Security Policy.
    *
--- a/netwerk/base/nsSimpleURI.cpp
+++ b/netwerk/base/nsSimpleURI.cpp
@@ -192,26 +192,22 @@ nsSimpleURI::GetHasRef(bool *result)
 }
 
 NS_IMETHODIMP
 nsSimpleURI::SetSpec(const nsACString &aSpec)
 {
     NS_ENSURE_STATE(mMutable);
     
     const nsAFlatCString& flat = PromiseFlatCString(aSpec);
-    const char* specPtr = flat.get();
 
     // filter out unexpected chars "\r\n\t" if necessary
     nsAutoCString filteredSpec;
-    int32_t specLen;
-    if (net_FilterURIString(specPtr, filteredSpec)) {
-        specPtr = filteredSpec.get();
-        specLen = filteredSpec.Length();
-    } else
-        specLen = flat.Length();
+    net_FilterURIString(flat, filteredSpec);
+    const char* specPtr = filteredSpec.get();
+    int32_t specLen = filteredSpec.Length();
 
     // nsSimpleURI currently restricts the charset to US-ASCII
     nsAutoCString spec;
     NS_EscapeURL(specPtr, specLen, esc_OnlyNonASCII|esc_AlwaysCopy, spec);
 
     int32_t colonPos = spec.FindChar(':');
     if (colonPos < 0 || !net_IsValidScheme(spec.get(), colonPos))
         return NS_ERROR_MALFORMED_URI;
--- a/netwerk/base/nsStandardURL.cpp
+++ b/netwerk/base/nsStandardURL.cpp
@@ -1288,47 +1288,35 @@ IsSpecialProtocol(const nsACString &inpu
 }
 
 NS_IMETHODIMP
 nsStandardURL::SetSpec(const nsACString &input)
 {
     ENSURE_MUTABLE();
 
     const nsPromiseFlatCString &flat = PromiseFlatCString(input);
-    const char *spec = flat.get();
-    int32_t specLength = flat.Length();
-
-    LOG(("nsStandardURL::SetSpec [spec=%s]\n", spec));
-
-    if (!spec || !*spec)
-        return NS_ERROR_MALFORMED_URI;
+    LOG(("nsStandardURL::SetSpec [spec=%s]\n", flat.get()));
 
     if (input.Length() > (uint32_t) net_GetURLMaxLength()) {
         return NS_ERROR_MALFORMED_URI;
     }
 
-    // NUL characters aren't allowed
-    // \r\n\t are stripped out instead of returning error(see below)
-    if (input.Contains('\0')) {
+    // filter out unexpected chars "\r\n\t" if necessary
+    nsAutoCString filteredURI;
+    net_FilterURIString(flat, filteredURI);
+
+    if (filteredURI.Length() == 0) {
         return NS_ERROR_MALFORMED_URI;
     }
 
     // Make a backup of the curent URL
     nsStandardURL prevURL(false,false);
     prevURL.CopyMembers(this, eHonorRef);
     Clear();
 
-    // filter out unexpected chars "\r\n\t" if necessary
-    nsAutoCString filteredURI;
-    if (!net_FilterURIString(spec, filteredURI)) {
-        // Copy the content into filteredURI even if no whitespace was stripped.
-        // We need a non-const buffer to perform backslash replacement.
-        filteredURI = input;
-    }
-
     if (IsSpecialProtocol(filteredURI)) {
         // Bug 652186: Replace all backslashes with slashes when parsing paths
         // Stop when we reach the query or the hash.
         nsAutoCString::iterator start;
         nsAutoCString::iterator end;
         filteredURI.BeginWriting(start);
         filteredURI.EndWriting(end);
         while (start != end) {
@@ -1337,19 +1325,18 @@ nsStandardURL::SetSpec(const nsACString 
             }
             if (*start == '\\') {
                 *start = '/';
             }
             start++;
         }
     }
 
-    spec = filteredURI.get();
-    specLength = filteredURI.Length();
-
+    const char *spec = filteredURI.get();
+    int32_t specLength = filteredURI.Length();
 
     // parse the given URL...
     nsresult rv = ParseURL(spec, specLength);
     if (NS_SUCCEEDED(rv)) {
         // finally, use the URLSegment member variables to build a normalized
         // copy of |spec|
         rv = BuildNormalizedSpec(spec);
     }
@@ -2117,29 +2104,22 @@ nsresult nsStandardURL::CopyMembers(nsSt
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsStandardURL::Resolve(const nsACString &in, nsACString &out)
 {
     const nsPromiseFlatCString &flat = PromiseFlatCString(in);
-    const char *relpath = flat.get();
-
     // filter out unexpected chars "\r\n\t" if necessary
     nsAutoCString buf;
-    int32_t relpathLen;
-    if (!net_FilterURIString(relpath, buf)) {
-        // Copy the content into filteredURI even if no whitespace was stripped.
-        // We need a non-const buffer to perform backslash replacement.
-        buf = in;
-    }
-
-    relpath = buf.get();
-    relpathLen = buf.Length();
+    net_FilterURIString(flat, buf);
+
+    const char *relpath = buf.get();
+    int32_t relpathLen = buf.Length();
 
     char *result = nullptr;
 
     LOG(("nsStandardURL::Resolve [this=%p spec=%s relpath=%s]\n",
         this, mSpec.get(), relpath));
 
     NS_ASSERTION(mParser, "no parser: unitialized");
 
@@ -2535,17 +2515,17 @@ nsStandardURL::SetFilePath(const nsACStr
         return SetPath(flat);
 
     if (filepath && *filepath) {
         nsAutoCString spec;
         uint32_t dirPos, basePos, extPos;
         int32_t dirLen, baseLen, extLen;
         nsresult rv;
 
-        rv = mParser->ParseFilePath(filepath, -1,
+        rv = mParser->ParseFilePath(filepath, flat.Length(),