Merge m-c to b2g-inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 12 Sep 2013 20:41:54 -0400
changeset 159827 b9029b1de41096eebd6808f169464a5ee38215a4
parent 159826 1c54974d338d1ca1e5e2db29cc004bb03da4455f (current diff)
parent 159779 cdfeb4aaea9ca1bcb3e208e743c0d0c37ae38ba8 (diff)
child 159862 23f0185620af555fa91008c4882705fbf9285fa1
child 159880 7b85565949af4d49fcf14d104bdb9fbb330c24a2
child 159887 b56ae6533034867fa712d26f255a8d699b90ba14
child 170308 08b462e660a04547459a2bc2f7bf8692377cd94b
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone26.0a1
first release with
nightly linux32
b9029b1de410 / 26.0a1 / 20130913030201 / files
nightly linux64
b9029b1de410 / 26.0a1 / 20130913030201 / files
nightly mac
b9029b1de410 / 26.0a1 / 20130913030201 / files
nightly win32
b9029b1de410 / 26.0a1 / 20130913030201 / files
nightly win64
b9029b1de410 / 26.0a1 / 20130913030201 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to b2g-inbound.
b2g/chrome/content/shell.js
gfx/layers/GrallocImages.cpp
mobile/android/base/Favicons.java
mobile/android/base/tests/testWebContentContextMenu.java.in
toolkit/components/jsdownloads/test/unit/test_DownloadImport.js
tools/profiler/SQLiteInterposer.cpp
tools/profiler/SQLiteInterposer.h
xpcom/base/MapsMemoryReporter.cpp
xpcom/base/MapsMemoryReporter.h
--- a/accessible/tests/mochitest/events.js
+++ b/accessible/tests/mochitest/events.js
@@ -288,21 +288,22 @@ function eventQueue(aEventType)
 
   /**
    * Process next invoker.
    */
   this.processNextInvoker = function eventQueue_processNextInvoker()
   {
     // Some scenario was matched, we wait on next invoker processing.
     if (this.mNextInvokerStatus == kInvokerCanceled) {
-      this.mNextInvokerStatus = kInvokerNotScheduled;
+      this.setInvokerStatus(kInvokerNotScheduled,
+                            "scenario was matched, wait for next invoker activation");
       return;
     }
 
-    this.mNextInvokerStatus = kInvokerNotScheduled;
+    this.setInvokerStatus(kInvokerNotScheduled, "the next invoker is processed now");
 
     // Finish processing of the current invoker if any.
     var testFailed = false;
 
     var invoker = this.getInvoker();
     if (invoker) {
       if ("finalCheck" in invoker)
         invoker.finalCheck();
@@ -428,17 +429,17 @@ function eventQueue(aEventType)
 
     if (this.hasUnexpectedEventsScenario())
       this.processNextInvokerInTimeout(true);
   }
 
   this.processNextInvokerInTimeout =
     function eventQueue_processNextInvokerInTimeout(aUncondProcess)
   {
-    this.mNextInvokerStatus = kInvokerPending;
+    this.setInvokerStatus(kInvokerPending, "Process next invoker in timeout");
 
     // No need to wait extra timeout when a) we know we don't need to do that
     // and b) there's no any single unexpected event.
     if (!aUncondProcess && this.areAllEventsExpected()) {
       // We need delay to avoid events coalesce from different invokers.
       var queue = this;
       SimpleTest.executeSoon(function() { queue.processNextInvoker(); });
       return;
@@ -536,25 +537,32 @@ function eventQueue(aEventType)
 
     if (hasMatchedCheckers) {
       var invoker = this.getInvoker();
       if ("check" in invoker)
         invoker.check(aEvent);
     }
 
     // If we don't have more events to wait then schedule next invoker.
-    if (this.hasMatchedScenario() &&
-        (this.mNextInvokerStatus == kInvokerNotScheduled)) {
-      this.processNextInvokerInTimeout();
+    if (this.hasMatchedScenario()) {
+      if (this.mNextInvokerStatus == kInvokerNotScheduled) {
+        this.processNextInvokerInTimeout();
+
+      } else if (this.mNextInvokerStatus == kInvokerCanceled) {
+        this.setInvokerStatus(kInvokerPending,
+                              "Full match. Void the cancelation of next invoker processing");
+      }
       return;
     }
 
     // If we have scheduled a next invoker then cancel in case of match.
-    if ((this.mNextInvokerStatus == kInvokerPending) && hasMatchedCheckers)
-      this.mNextInvokerStatus = kInvokerCanceled;
+    if ((this.mNextInvokerStatus == kInvokerPending) && hasMatchedCheckers) {
+      this.setInvokerStatus(kInvokerCanceled,
+                            "Cancel the scheduled invoker in case of match");
+    }
   }
 
   // Helpers
   this.processMatchedChecker =
     function eventQueue_function(aEvent, aMatchedChecker, aScenarioIdx, aEventIdx)
   {
     aMatchedChecker.wasCaught++;
 
@@ -617,40 +625,47 @@ function eventQueue(aEventType)
         if (eventSeq[idx].unexpected)
           return false;
       }
     }
 
     return true;
   }
 
+  this.isUnexpectedEventScenario =
+    function eventQueue_isUnexpectedEventsScenario(aScenario)
+  {
+    for (var idx = 0; idx < aScenario.length; idx++) {
+      if (!aScenario[idx].unexpected)
+        break;
+    }
+
+    return idx == aScenario.length;
+  }
+
   this.hasUnexpectedEventsScenario =
     function eventQueue_hasUnexpectedEventsScenario()
   {
     if (this.getInvoker().noEventsOnAction)
       return true;
 
     for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
-      var eventSeq = this.mScenarios[scnIdx];
-      for (var idx = 0; idx < eventSeq.length; idx++) {
-        if (!eventSeq[idx].unexpected)
-          break;
-      }
-      if (idx == eventSeq.length)
+      if (this.isUnexpectedEventScenario(this.mScenarios[scnIdx]))
         return true;
     }
 
     return false;
   }
-  
+
   this.hasMatchedScenario =
     function eventQueue_hasMatchedScenario()
   {
     for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
-      if (!this.areExpectedEventsLeft(this.mScenarios[scnIdx]))
+      var scn = this.mScenarios[scnIdx];
+      if (!this.isUnexpectedEventScenario(scn) && !this.areExpectedEventsLeft(scn))
         return true;
     }
     return false;
   }
 
   this.getInvoker = function eventQueue_getInvoker()
   {
     return this.mInvokers[this.mIndex];
@@ -770,16 +785,24 @@ function eventQueue(aEventType)
   {
     if ("getID" in aChecker)
       return aChecker.getID();
 
     var invoker = this.getInvoker();
     return invoker.getID();
   }
 
+  this.setInvokerStatus = function eventQueue_setInvokerStatus(aStatus, aLogMsg)
+  {
+    this.mNextInvokerStatus = aStatus;
+
+    // Uncomment it to debug invoker processing logic.
+    //gLogger.log(eventQueue.invokerStatusToMsg(aStatus, aLogMsg));
+  }
+
   this.mDefEventType = aEventType;
 
   this.mInvokers = new Array();
   this.mIndex = -1;
   this.mScenarios = null;
 
   this.mNextInvokerStatus = kInvokerNotScheduled;
 }
@@ -867,63 +890,63 @@ eventQueue.isSameEvent = function eventQ
   // We don't have stored info about handled event other than its type and
   // target, thus we should filter text change and state change events since
   // they may occur on the same element because of complex changes.
   return this.compareEvents(aChecker, aEvent) &&
     !(aEvent instanceof nsIAccessibleTextChangeEvent) &&
     !(aEvent instanceof nsIAccessibleStateChangeEvent);
 }
 
-eventQueue.logEvent = function eventQueue_logEvent(aOrigEvent, aMatchedChecker,
-                                                   aScenarioIdx, aEventIdx,
-                                                   aAreExpectedEventsLeft,
-                                                   aInvokerStatus)
+eventQueue.invokerStatusToMsg =
+  function eventQueue_invokerStatusToMsg(aInvokerStatus, aMsg)
 {
-  if (!gLogger.isEnabled()) // debug stuff
-    return;
-
-  // Dump DOM event information. Skip a11y event since it is dumped by
-  // gA11yEventObserver.
-  if (aOrigEvent instanceof nsIDOMEvent) {
-    var info = "Event type: " + eventQueue.getEventTypeAsString(aOrigEvent);
-    info += ". Target: " + eventQueue.getEventTargetDescr(aOrigEvent);
-    gLogger.logToDOM(info);
-  }
-
-  var msg = "unhandled expected events: " + aAreExpectedEventsLeft +
-    ", invoker status: ";
+  var msg = "invoker status: ";
   switch (aInvokerStatus) {
     case kInvokerNotScheduled:
       msg += "not scheduled";
       break;
     case kInvokerPending:
       msg += "pending";
       break;
     case kInvokerCanceled:
       msg += "canceled";
       break;
   }
 
-  gLogger.logToConsole(msg);
-  gLogger.logToDOM(msg);
+  if (aMsg)
+    msg += " (" + aMsg + ")";
+
+  return msg;
+}
 
-  if (!aMatchedChecker)
-    return;
+eventQueue.logEvent = function eventQueue_logEvent(aOrigEvent, aMatchedChecker,
+                                                   aScenarioIdx, aEventIdx,
+                                                   aAreExpectedEventsLeft,
+                                                   aInvokerStatus)
+{
+  // Dump DOM event information. Skip a11y event since it is dumped by
+  // gA11yEventObserver.
+  if (aOrigEvent instanceof nsIDOMEvent) {
+    var info = "Event type: " + eventQueue.getEventTypeAsString(aOrigEvent);
+    info += ". Target: " + eventQueue.getEventTargetDescr(aOrigEvent);
+    gLogger.logToDOM(info);
+  }
 
-  var msg = "EQ: ";
-  var emphText = "matched ";
+  var infoMsg = "unhandled expected events: " + aAreExpectedEventsLeft +
+    ", "  + eventQueue.invokerStatusToMsg(aInvokerStatus);
 
   var currType = eventQueue.getEventTypeAsString(aMatchedChecker);
   var currTargetDescr = eventQueue.getEventTargetDescr(aMatchedChecker);
   var consoleMsg = "*****\nScenario " + aScenarioIdx + 
-    ", event " + aEventIdx + " matched: " + currType + "\n*****";
+    ", event " + aEventIdx + " matched: " + currType + "\n" + infoMsg + "\n*****";
   gLogger.logToConsole(consoleMsg);
 
-  msg += " event, type: " + currType + ", target: " + currTargetDescr;
-
+  var emphText = "matched ";
+  var msg = "EQ event, type: " + currType + ", target: " + currTargetDescr +
+    ", " + infoMsg;
   gLogger.logToDOM(msg, true, emphText);
 }
 
 
 ////////////////////////////////////////////////////////////////////////////////
 // Action sequence
 
 /**
--- a/accessible/tests/mochitest/states/test_doc_busy.html
+++ b/accessible/tests/mochitest/states/test_doc_busy.html
@@ -15,17 +15,17 @@
   <script type="application/javascript"
           src="../role.js"></script>
   <script type="application/javascript"
           src="../states.js"></script>
   <script type="application/javascript"
           src="../events.js"></script>
 
   <script type="application/javascript">
-    gA11yEventDumpToConsole = true; // debugging stuff
+    //gA11yEventDumpToConsole = true; // debugging stuff
 
     function loadFile()
     {
       var eventSeq = [
         new stateChangeChecker(STATE_BUSY, false, true, document, null, false, true),
         new stateChangeChecker(STATE_BUSY, false, false, document)
       ];
       defineScenario(this, eventSeq); // both events were fired
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -742,19 +742,33 @@ pref("captivedetect.canonicalURL", "http
 pref("captivedetect.canonicalContent", "success\n");
 
 // The url of the manifest we use for ADU pings.
 pref("ping.manifestURL", "https://marketplace.firefox.com/packaged.webapp");
 
 // Enable the disk space watcher
 pref("disk_space_watcher.enabled", true);
 
+// SNTP preferences.
+pref("network.sntp.maxRetryCount", 10);
+pref("network.sntp.refreshPeriod", 86400); // In seconds.
+pref("network.sntp.pools", // Servers separated by ';'.
+     "0.pool.ntp.org;1.pool.ntp.org;2.pool.ntp.org;3.pool.ntp.org");
+pref("network.sntp.port", 123);
+pref("network.sntp.timeout", 30); // In seconds.
+
 // Enable promise
 pref("dom.promise.enabled", false);
 
+// DOM Inter-App Communication API.
+#ifdef MOZ_WIDGET_GONK
+// Enable this only for gonk-specific build but not for desktop build.
+pref("dom.inter-app-communication-api.enabled", true);
+#endif
+
 // Allow ADB to run for this many hours before disabling
 // (only applies when marionette is disabled)
 // 0 disables the timer.
 pref("b2g.adb.timeout-hours", 12);
 
 // Absolute path to the devtool unix domain socket file used
 // to communicate with a usb cable via adb forward
 pref("devtools.debugger.unix-domain-socket", "/data/local/debugger-socket");
--- a/b2g/chrome/content/forms.js
+++ b/b2g/chrome/content/forms.js
@@ -1064,92 +1064,100 @@ function replaceSurroundingText(element,
     editor.insertText(text);
   }
 }
 
 let CompositionManager =  {
   _isStarted: false,
   _text: '',
   _clauseAttrMap: {
-    'raw-input': domWindowUtils.COMPOSITION_ATTR_RAWINPUT,
-    'selected-raw-text': domWindowUtils.COMPOSITION_ATTR_SELECTEDRAWTEXT,
-    'converted-text': domWindowUtils.COMPOSITION_ATTR_CONVERTEDTEXT,
-    'selected-converted-text': domWindowUtils.COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT
+    'raw-input':
+      Ci.nsICompositionStringSynthesizer.ATTR_RAWINPUT,
+    'selected-raw-text':
+      Ci.nsICompositionStringSynthesizer.ATTR_SELECTEDRAWTEXT,
+    'converted-text':
+      Ci.nsICompositionStringSynthesizer.ATTR_CONVERTEDTEXT,
+    'selected-converted-text':
+      Ci.nsICompositionStringSynthesizer.ATTR_SELECTEDCONVERTEDTEXT
   },
 
   setComposition: function cm_setComposition(element, text, cursor, clauses) {
     // Check parameters.
     if (!element) {
       return;
     }
     let len = text.length;
-    if (cursor < 0) {
-      cursor = 0;
-    } else if (cursor > len) {
+    if (cursor > len) {
       cursor = len;
     }
-    let clauseLens = [len, 0, 0];
-    let clauseAttrs = [domWindowUtils.COMPOSITION_ATTR_RAWINPUT,
-                       domWindowUtils.COMPOSITION_ATTR_RAWINPUT,
-                       domWindowUtils.COMPOSITION_ATTR_RAWINPUT];
+    let clauseLens = [];
+    let clauseAttrs = [];
     if (clauses) {
       let remainingLength = len;
-      // Currently we don't support 4 or more clauses composition string.
-      let clauseNum = Math.min(3, clauses.length);
-      for (let i = 0; i < clauseNum; i++) {
+      for (let i = 0; i < clauses.length; i++) {
         if (clauses[i]) {
           let clauseLength = clauses[i].length || 0;
           // Make sure the total clauses length is not bigger than that of the
           // composition string.
           if (clauseLength > remainingLength) {
             clauseLength = remainingLength;
           }
           remainingLength -= clauseLength;
-          clauseLens[i] = clauseLength;
-          clauseAttrs[i] = this._clauseAttrMap[clauses[i].selectionType] ||
-                           domWindowUtils.COMPOSITION_ATTR_RAWINPUT;
+          clauseLens.push(clauseLength);
+          clauseAttrs.push(this._clauseAttrMap[clauses[i].selectionType] ||
+                           Ci.nsICompositionStringSynthesizer.ATTR_RAWINPUT);
         }
       }
       // If the total clauses length is less than that of the composition
       // string, extend the last clause to the end of the composition string.
       if (remainingLength > 0) {
-        clauseLens[2] += remainingLength;
+        clauseLens[clauseLens.length - 1] += remainingLength;
       }
+    } else {
+      clauseLens.push(len);
+      clauseAttrs.push(Ci.nsICompositionStringSynthesizer.ATTR_RAWINPUT);
     }
 
     // Start composition if need to.
     if (!this._isStarted) {
       this._isStarted = true;
       domWindowUtils.sendCompositionEvent('compositionstart', '', '');
       this._text = '';
     }
 
     // Update the composing text.
     if (this._text !== text) {
       this._text = text;
       domWindowUtils.sendCompositionEvent('compositionupdate', text, '');
     }
-    domWindowUtils.sendTextEvent(text,
-                                 clauseLens[0], clauseAttrs[0],
-                                 clauseLens[1], clauseAttrs[1],
-                                 clauseLens[2], clauseAttrs[2],
-                                 cursor, 0);
+    let compositionString = domWindowUtils.createCompositionStringSynthesizer();
+    compositionString.setString(text);
+    for (var i = 0; i < clauseLens.length; i++) {
+      compositionString.appendClause(clauseLens[i], clauseAttrs[i]);
+    }
+    if (cursor >= 0) {
+      compositionString.setCaret(cursor, 0);
+    }
+    compositionString.dispatchEvent();
   },
 
   endComposition: function cm_endComposition(text) {
     if (!this._isStarted) {
       return;
     }
     // Update the composing text.
     if (this._text !== text) {
       domWindowUtils.sendCompositionEvent('compositionupdate', text, '');
     }
+    let compositionString = domWindowUtils.createCompositionStringSynthesizer();
+    compositionString.setString(text);
     // Set the cursor position to |text.length| so that the text will be
     // committed before the cursor position.
-    domWindowUtils.sendTextEvent(text, 0, 0, 0, 0, 0, 0, text.length, 0);
+    compositionString.setCaret(text.length, 0);
+    compositionString.dispatchEvent();
     domWindowUtils.sendCompositionEvent('compositionend', text, '');
     this._text = '';
     this._isStarted = false;
   },
 
   // Composition ends due to external actions.
   onCompositionEnd: function cm_onCompositionEnd() {
     if (!this._isStarted) {
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -626,19 +626,28 @@ Services.obs.addObserver(function onSyst
   // We'll revisit this later if new kind of requests don't need to be cached.
   if (shell.needBufferOpenAppReq && msg.type !== 'activity') {
     shell.bufferedOpenAppReqs.push(msg);
     return;
   }
   shell.openAppForSystemMessage(msg);
 }, 'system-messages-open-app', false);
 
-Services.obs.addObserver(function(aSubject, aTopic, aData) {
+Services.obs.addObserver(function onInterAppCommConnect(subject, topic, data) {
+  data = JSON.parse(data);
+  shell.sendChromeEvent({ type: "inter-app-comm-permission",
+                          chromeEventID: data.callerID,
+                          manifestURL: data.manifestURL,
+                          keyword: data.keyword,
+                          peers: data.appsToSelect });
+}, 'inter-app-comm-select-app', false);
+
+Services.obs.addObserver(function onFullscreenOriginChange(subject, topic, data) {
   shell.sendChromeEvent({ type: "fullscreenoriginchange",
-                          fullscreenorigin: aData });
+                          fullscreenorigin: data });
 }, "fullscreen-origin-change", false);
 
 Services.obs.addObserver(function onWebappsStart(subject, topic, data) {
   shell.sendChromeEvent({ type: 'webapps-registry-start' });
 }, 'webapps-registry-start', false);
 
 Services.obs.addObserver(function onWebappsReady(subject, topic, data) {
   shell.sendChromeEvent({ type: 'webapps-registry-ready' });
@@ -695,16 +704,23 @@ var CustomEventManager = {
         Services.obs.notifyObservers(null, 'system-message-listener-ready', null);
         break;
       case 'remote-debugger-prompt':
         RemoteDebugger.handleEvent(detail);
         break;
       case 'captive-portal-login-cancel':
         CaptivePortalLoginHelper.handleEvent(detail);
         break;
+      case 'inter-app-comm-permission':
+        Services.obs.notifyObservers(null, 'inter-app-comm-select-app-result',
+          JSON.stringify({ callerID: detail.chromeEventID,
+                           keyword: detail.keyword,
+                           manifestURL: detail.manifestURL,
+                           selectedApps: detail.peers }));
+        break;
       case 'inputmethod-update-layouts':
         KeyboardHelper.handleEvent(detail);
         break;
     }
   }
 }
 
 var AlertsHelper = {
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -500,16 +500,21 @@
 @BINPATH@/components/Webapps.js
 @BINPATH@/components/Webapps.manifest
 @BINPATH@/components/AppsService.js
 @BINPATH@/components/AppsService.manifest
 @BINPATH@/components/Push.js
 @BINPATH@/components/Push.manifest
 @BINPATH@/components/PushServiceLauncher.js
 
+@BINPATH@/components/InterAppComm.manifest
+@BINPATH@/components/InterAppCommService.js
+@BINPATH@/components/InterAppConnection.js
+@BINPATH@/components/InterAppMessagePort.js
+
 @BINPATH@/components/nsDOMIdentity.js
 @BINPATH@/components/nsIDService.js
 @BINPATH@/components/Identity.manifest
 
 @BINPATH@/components/SystemMessageInternal.js
 @BINPATH@/components/SystemMessageManager.js
 @BINPATH@/components/SystemMessageManager.manifest
 
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -347,16 +347,20 @@ pref("browser.download.animateNotificati
 
 // This records whether or not the panel has been shown at least once.
 pref("browser.download.panel.shown", false);
 
 // This records whether or not at least one session with the Downloads Panel
 // enabled has been completed already.
 pref("browser.download.panel.firstSessionCompleted", false);
 
+#ifndef XP_MACOSX
+pref("browser.helperApps.deleteTempFileOnExit", true);
+#endif
+
 // search engines URL
 pref("browser.search.searchEnginesURL",      "https://addons.mozilla.org/%LOCALE%/firefox/search-engines/");
 
 // pointer to the default engine name
 pref("browser.search.defaultenginename",      "chrome://browser-region/locale/region.properties");
 
 // disable logging for the search service by default
 pref("browser.search.log", false);
--- a/browser/base/content/browser-thumbnails.js
+++ b/browser/base/content/browser-thumbnails.js
@@ -97,17 +97,17 @@ let gBrowserThumbnails = {
                                                    aRequest, aStateFlags, aStatus) {
     if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
         aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)
       this._delayedCapture(aBrowser);
   },
 
   _capture: function Thumbnails_capture(aBrowser) {
     if (this._shouldCapture(aBrowser))
-      PageThumbs.captureAndStore(aBrowser);
+      PageThumbs.captureAndStoreIfStale(aBrowser);
   },
 
   _delayedCapture: function Thumbnails_delayedCapture(aBrowser) {
     if (this._timeouts.has(aBrowser))
       clearTimeout(this._timeouts.get(aBrowser));
     else
       aBrowser.addEventListener("scroll", this, true);
 
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -539,17 +539,16 @@ browser[tabmodalPromptShowing] {
 }
 
 /* Status panel */
 
 statuspanel {
   -moz-binding: url("chrome://browser/content/tabbrowser.xml#statuspanel");
   position: fixed;
   margin-top: -3em;
-  left: 0;
   max-width: calc(100% - 5px);
   pointer-events: none;
 }
 
 statuspanel:-moz-locale-dir(ltr)[mirror],
 statuspanel:-moz-locale-dir(rtl):not([mirror]) {
   left: auto;
   right: 0;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -165,16 +165,20 @@ let gInitialPages = [
 #endif
 
 #ifdef MOZ_SERVICES_SYNC
 #include browser-syncui.js
 #endif
 
 XPCOMUtils.defineLazyGetter(this, "Win7Features", function () {
 #ifdef XP_WIN
+  // Bug 666808 - AeroPeek support for e10s
+  if (gMultiProcessBrowser)
+    return null;
+
   const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
   if (WINTASKBAR_CONTRACTID in Cc &&
       Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available) {
     let AeroPeek = Cu.import("resource:///modules/WindowsPreviewPerTab.jsm", {}).AeroPeek;
     return {
       onOpenWindow: function () {
         AeroPeek.onOpenWindow(window);
       },
@@ -1130,21 +1134,18 @@ var gBrowserInit = {
     placesContext.addEventListener("popupshowing", updateEditUIVisibility, false);
     placesContext.addEventListener("popuphiding", updateEditUIVisibility, false);
 #endif
 
     gBrowser.mPanelContainer.addEventListener("InstallBrowserTheme", LightWeightThemeWebInstaller, false, true);
     gBrowser.mPanelContainer.addEventListener("PreviewBrowserTheme", LightWeightThemeWebInstaller, false, true);
     gBrowser.mPanelContainer.addEventListener("ResetBrowserThemePreview", LightWeightThemeWebInstaller, false, true);
 
-    // Bug 666808 - AeroPeek support for e10s
-    if (!gMultiProcessBrowser) {
-      if (Win7Features)
-        Win7Features.onOpenWindow();
-    }
+    if (Win7Features)
+      Win7Features.onOpenWindow();
 
    // called when we go into full screen, even if initiated by a web page script
     window.addEventListener("fullscreen", onFullScreen, true);
 
     // Called when we enter DOM full-screen mode. Note we can already be in browser
     // full-screen mode when we enter DOM full-screen mode.
     window.addEventListener("MozEnteredDomFullscreen", onMozEnteredDomFullscreen, true);
 
@@ -3650,18 +3651,17 @@ var XULBrowserWindow = {
     delete this.stopCommand;
     return this.stopCommand = document.getElementById("Browser:Stop");
   },
   get reloadCommand () {
     delete this.reloadCommand;
     return this.reloadCommand = document.getElementById("Browser:Reload");
   },
   get statusTextField () {
-    delete this.statusTextField;
-    return this.statusTextField = document.getElementById("statusbar-display");
+    return gBrowser.getStatusPanel();
   },
   get isImage () {
     delete this.isImage;
     return this.isImage = document.getElementById("isImage");
   },
 
   init: function () {
     this.throbberElement = document.getElementById("navigator-throbber");
@@ -3671,17 +3671,16 @@ var XULBrowserWindow = {
     this.onSecurityChange(null, null, securityUI.state);
   },
 
   destroy: function () {
     // XXXjag to avoid leaks :-/, see bug 60729
     delete this.throbberElement;
     delete this.stopCommand;
     delete this.reloadCommand;
-    delete this.statusTextField;
     delete this.statusText;
   },
 
   setJSStatus: function () {
     // unsupported
   },
 
   setDefaultStatus: function (status) {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -1021,17 +1021,16 @@
     <vbox id="appcontent" flex="1">
       <tabbrowser id="content" disablehistory="true"
                   flex="1" contenttooltip="aHTMLTooltip"
                   tabcontainer="tabbrowser-tabs"
                   contentcontextmenu="contentAreaContextMenu"
                   autocompletepopup="PopupAutoComplete"
                   selectpopup="ContentSelectDropdown"/>
       <chatbar id="pinnedchats" layer="true" mousethrough="always" hidden="true"/>
-      <statuspanel id="statusbar-display" inactive="true"/>
     </vbox>
     <splitter id="social-sidebar-splitter"
               class="chromeclass-extrachrome sidebar-splitter"
               observes="socialSidebarBroadcaster"/>
     <vbox id="social-sidebar-box"
           class="chromeclass-extrachrome"
           observes="socialSidebarBroadcaster"
           persist="width">
--- a/browser/base/content/newtab/sites.js
+++ b/browser/base/content/newtab/sites.js
@@ -129,17 +129,17 @@ Site.prototype = {
     link.setAttribute("href", url);
     this._querySelector(".newtab-title").textContent = title;
 
     if (this.isPinned())
       this._updateAttributes(true);
 #ifndef RELEASE_BUILD
     // request a staleness check for the thumbnail, which will cause page.js
     // to be notified and call our refreshThumbnail() method.
-    BackgroundPageThumbs.captureIfStale(this.url);
+    BackgroundPageThumbs.captureIfMissing(this.url);
     // but still display whatever thumbnail might be available now.
 #endif
     this.refreshThumbnail();
   },
 
   /**
    * Refreshes the thumbnail for the site.
    */
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -317,21 +317,18 @@ Sanitizer.prototype = {
               // Convert microseconds back to milliseconds for date comparisons.
               let rangeBeginMs = this.range[0] / 1000;
               let rangeEndMs = this.range[1] / 1000;
               filterByTime = download => download.startTime >= rangeBeginMs &&
                                          download.startTime <= rangeEndMs;
             }
 
             // Clear all completed/cancelled downloads
-            let publicList = yield Downloads.getPublicDownloadList();
-            publicList.removeFinished(filterByTime);
-
-            let privateList = yield Downloads.getPrivateDownloadList();
-            privateList.removeFinished(filterByTime);
+            let list = yield Downloads.getList(Downloads.ALL);
+            list.removeFinished(filterByTime);
           }.bind(this)).then(null, Components.utils.reportError);
         }
         else {
           var dlMgr = Components.classes["@mozilla.org/download-manager;1"]
                                 .getService(Components.interfaces.nsIDownloadManager);
 
           if (this.range) {
             // First, remove the completed/cancelled downloads
--- a/browser/base/content/socialmarks.xml
+++ b/browser/base/content/socialmarks.xml
@@ -193,21 +193,22 @@
       <handler event="popupshown"><![CDATA[
         // because the panel may be preloaded, we need to size the panel when
         // showing as well as after load
         let sizeSocialPanelToContent = Cu.import("resource:///modules/Social.jsm", {}).sizeSocialPanelToContent;
         if (!this._loading && this.contentDocument.readyState == "complete") {
           this.dispatchPanelEvent("socialFrameShow");
           sizeSocialPanelToContent(this.panel, this.content);
         } else {
-          this.content.addEventListener("load", function panelBrowserOnload(e) {
+          let panelBrowserOnload = () => {
             this.content.removeEventListener("load", panelBrowserOnload, true);
             this.dispatchPanelEvent("socialFrameShow");
             sizeSocialPanelToContent(this.panel, this.content);
-          }.bind(this), true);
+          };
+          this.content.addEventListener("load", panelBrowserOnload, true);
         }
       ]]></handler>
       <handler event="popuphidden"><![CDATA[
         this.dispatchPanelEvent("socialFrameHide");
       ]]></handler>
       <handler event="command"><![CDATA[
         this.markCurrentPage();
       ]]></handler>
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -177,16 +177,37 @@
           let event = document.createEvent("Events");
           event.initEvent("TabFindInitialized", true, false);
           aTab.dispatchEvent(event);
 
           return findBar;
         ]]></body>
       </method>
 
+      <method name="getStatusPanel">
+        <body><![CDATA[
+          if (!this._statusPanel) {
+            this._statusPanel = document.createElementNS(this.namespaceURI, "statuspanel");
+            this._statusPanel.setAttribute("inactive", "true");
+            this._appendStatusPanel();
+          }
+          return this._statusPanel;
+        ]]></body>
+      </method>
+
+      <method name="_appendStatusPanel">
+        <body><![CDATA[
+          if (this._statusPanel) {
+            let browser = this.selectedBrowser;
+            let browserContainer = this.getBrowserContainer(browser);
+            browserContainer.insertBefore(this._statusPanel, browser.parentNode.nextSibling);
+          }
+        ]]></body>
+      </method>
+
       <method name="updateWindowResizers">
         <body><![CDATA[
           if (!window.gShowPageResizers)
             return;
 
           var show = document.getElementById("addon-bar").collapsed &&
                      window.windowState == window.STATE_NORMAL;
           for (let i = 0; i < this.browsers.length; i++) {
@@ -983,16 +1004,18 @@
             if (backForwardContainer) {
               backForwardContainer.setAttribute("switchingtabs", "true");
               window.addEventListener("MozAfterPaint", function removeSwitchingtabsAttr() {
                 window.removeEventListener("MozAfterPaint", removeSwitchingtabsAttr);
                 backForwardContainer.removeAttribute("switchingtabs");
               });
             }
 
+            this._appendStatusPanel();
+
             if (updatePageReport)
               this.mCurrentBrowser.updatePageReport();
 
             // Update the URL bar.
             var loc = this.mCurrentBrowser.currentURI;
 
             // Bug 666809 - SecurityUI support for e10s
             var webProgress = this.mCurrentBrowser.webProgress;
--- a/browser/base/content/test/browser_sanitize-timespans.js
+++ b/browser/base/content/test/browser_sanitize-timespans.js
@@ -91,17 +91,17 @@ function onHistoryReady() {
   itemPrefs.setBoolPref("cache", false);
   itemPrefs.setBoolPref("cookies", false);
   itemPrefs.setBoolPref("formdata", true);
   itemPrefs.setBoolPref("offlineApps", false);
   itemPrefs.setBoolPref("passwords", false);
   itemPrefs.setBoolPref("sessions", false);
   itemPrefs.setBoolPref("siteSettings", false);
 
-  let publicList = yield Downloads.getPublicDownloadList();
+  let publicList = yield Downloads.getList(Downloads.PUBLIC);
   let downloadPromise = promiseDownloadRemoved(publicList);
 
   // Clear 10 minutes ago
   s.range = [now_uSec - 10*60*1000000, now_uSec];
   s.sanitize();
   s.range = null;
 
   yield promiseFormHistoryRemoved();
@@ -607,17 +607,17 @@ function setupFormHistory() {
   yield countEntries("4hour10minutes", "Checking for 4hour10minutes form history entry creation", checkOne);
   yield countEntries("today", "Checking for today form history entry creation", checkOne);
   yield countEntries("b4today", "Checking for b4today form history entry creation", checkOne);
   is(checks, 9, "9 checks made");
 }
 
 function setupDownloads() {
 
-  let publicList = yield Downloads.getPublicDownloadList();
+  let publicList = yield Downloads.getList(Downloads.PUBLIC);
 
   let download = yield Downloads.createDownload({
     source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
     target: "fakefile-10-minutes"
   });
   download.startTime = new Date(now_mSec - 10 * kMsecPerMin), // 10 minutes ago
   download.canceled = true;
   publicList.add(download);
--- a/browser/base/content/test/browser_sanitizeDialog.js
+++ b/browser/base/content/test/browser_sanitizeDialog.js
@@ -907,17 +907,17 @@ WindowHelper.prototype = {
 
 /**
  * Adds a download to history.
  *
  * @param aMinutesAgo
  *        The download will be downloaded this many minutes ago
  */
 function addDownloadWithMinutesAgo(aExpectedPathList, aMinutesAgo) {
-  let publicList = yield Downloads.getPublicDownloadList();
+  let publicList = yield Downloads.getList(Downloads.PUBLIC);
 
   let name = "fakefile-" + aMinutesAgo + "-minutes-ago";
   let download = yield Downloads.createDownload({
     source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
     target: name
   });
   download.startTime = new Date(now_mSec - (aMinutesAgo * kMsecPerMin));
   download.canceled = true;
@@ -979,17 +979,17 @@ function formNameExists(name)
 function blankSlate() {
   PlacesUtils.bhistory.removeAllPages();
 
   // The promise is resolved only when removing both downloads and form history are done.
   let deferred = Promise.defer();
   let formHistoryDone = false, downloadsDone = false;
 
   Task.spawn(function deleteAllDownloads() {
-    let publicList = yield Downloads.getPublicDownloadList();
+    let publicList = yield Downloads.getList(Downloads.PUBLIC);
     let downloads = yield publicList.getAll();
     for (let download of downloads) {
       publicList.remove(download);
       yield download.finalize(true);
     }
     downloadsDone = true;
     if (formHistoryDone) {
       deferred.resolve();
@@ -1032,17 +1032,17 @@ function boolPrefIs(aPrefName, aExpected
  *
  * @param  aPath
  *         The path of the download to check
  * @return True if the download exists, false otherwise
  */
 function downloadExists(aPath)
 {
   return Task.spawn(function() {
-    let publicList = yield Downloads.getPublicDownloadList();
+    let publicList = yield Downloads.getList(Downloads.PUBLIC);
     let listArray = yield publicList.getAll();
     throw new Task.Result(listArray.some(i => i.target.path == aPath));
   });
 }
 
 /**
  * Runs the next test in the gAllTests array.  If all tests have been run,
  * finishes the entire suite.
--- a/browser/components/downloads/src/DownloadsCommon.jsm
+++ b/browser/components/downloads/src/DownloadsCommon.jsm
@@ -635,18 +635,18 @@ DownloadsDataCtor.prototype = {
    *        called, and we must ensure to register our listeners before the
    *        getService call for the Download Manager returns.
    */
   initializeDataLink: function DD_initializeDataLink(aDownloadManagerService)
   {
     // Start receiving real-time events.
     if (DownloadsCommon.useJSTransfer) {
       if (!this._dataLinkInitialized) {
-        let promiseList = this._isPrivate ? Downloads.getPrivateDownloadList()
-                                          : Downloads.getPublicDownloadList();
+        let promiseList = Downloads.getList(this._isPrivate ? Downloads.PRIVATE
+                                                            : Downloads.PUBLIC);
         promiseList.then(list => list.addView(this)).then(null, Cu.reportError);
         this._dataLinkInitialized = true;
       }
     } else {
       aDownloadManagerService.addPrivacyAwareListener(this);
       Services.obs.addObserver(this, "download-manager-remove-download-guid",
                                false);
     }
@@ -692,18 +692,18 @@ DownloadsDataCtor.prototype = {
   },
 
   /**
    * Asks the back-end to remove finished downloads from the list.
    */
   removeFinished: function DD_removeFinished()
   {
     if (DownloadsCommon.useJSTransfer) {
-      let promiseList = this._isPrivate ? Downloads.getPrivateDownloadList()
-                                        : Downloads.getPublicDownloadList();
+      let promiseList = Downloads.getList(this._isPrivate ? Downloads.PRIVATE
+                                                          : Downloads.PUBLIC);
       promiseList.then(list => list.removeFinished())
                  .then(null, Cu.reportError);
     } else {
       if (this._isPrivate) {
         Services.downloads.cleanUpPrivate();
       } else {
         Services.downloads.cleanUp();
       }
@@ -1710,22 +1710,20 @@ DownloadsDataItem.prototype = {
     }.bind(this));
   },
 
   /**
    * Remove the download.
    */
   remove: function DDI_remove() {
     if (DownloadsCommon.useJSTransfer) {
-      let promiseList = this._download.source.isPrivate
-                          ? Downloads.getPrivateDownloadList()
-                          : Downloads.getPublicDownloadList();
-      promiseList.then(list => list.remove(this._download))
-                 .then(() => this._download.finalize(true))
-                 .then(null, Cu.reportError);
+      Downloads.getList(Downloads.ALL)
+               .then(list => list.remove(this._download))
+               .then(() => this._download.finalize(true))
+               .then(null, Cu.reportError);
       return;
     }
 
     this.getDownload(function (aDownload) {
       if (this.inProgress) {
         aDownload.cancel();
         this._ensureLocalFileRemoved();
       }
--- a/browser/components/downloads/test/browser/head.js
+++ b/browser/components/downloads/test/browser/head.js
@@ -67,33 +67,33 @@ function promisePanelOpened()
   };
 
   return deferred.promise;
 }
 
 function task_resetState()
 {
   // Remove all downloads.
-  let publicList = yield Downloads.getPublicDownloadList();
+  let publicList = yield Downloads.getList(Downloads.PUBLIC);
   let downloads = yield publicList.getAll();
   for (let download of downloads) {
     publicList.remove(download);
     yield download.finalize(true);
   }
 
   DownloadsPanel.hidePanel();
 
   yield promiseFocus();
 }
 
 function task_addDownloads(aItems)
 {
   let startTimeMs = Date.now();
 
-  let publicList = yield Downloads.getPublicDownloadList();
+  let publicList = yield Downloads.getList(Downloads.PUBLIC);
   for (let item of aItems) {
     publicList.add(yield Downloads.createDownload({
       source: "http://www.example.com/test-download.txt",
       target: gTestTargetFile,
       succeeded: item.state == nsIDM.DOWNLOAD_FINISHED,
       canceled: item.state == nsIDM.DOWNLOAD_CANCELED ||
                 item.state == nsIDM.DOWNLOAD_PAUSED,
       error: item.state == nsIDM.DOWNLOAD_FAILED ? new Error("Failed.") : null,
--- a/browser/devtools/app-manager/content/index.xul
+++ b/browser/devtools/app-manager/content/index.xul
@@ -18,18 +18,18 @@
         fullscreenbutton="true"
         screenX="4" screenY="4"
         width="800" height="600"
         persist="screenX screenY width height sizemode">
 
   <vbox flex="1">
     <hbox id="content" flex="1">
       <vbox id="tabs">
-        <button class="button projects-button" onclick="selectTab('projects')">&index.projects;</button>
-        <button class="button device-button" onclick="selectTab('device')">&index.device;</button>
+        <button class="button projects-button" onclick="selectTab('projects')">&index.projects2;</button>
+        <button class="button device-button" onclick="selectTab('device')">&index.device2;</button>
       </vbox>
       <hbox id="tab-panels" flex="1">
         <iframe flex="1" class="panel projects-panel" src="chrome://browser/content/devtools/app-manager/projects.xhtml"/>
         <iframe flex="1" class="panel device-panel" src="chrome://browser/content/devtools/app-manager/device.xhtml"/>
       </hbox>
     </hbox>
     <iframe id="connection-footer" src="chrome://browser/content/devtools/app-manager/connection-footer.xhtml"></iframe>
   </vbox>
--- a/browser/devtools/debugger/debugger-toolbar.js
+++ b/browser/devtools/debugger/debugger-toolbar.js
@@ -663,17 +663,17 @@ let StackFrameUtils = {
    * to display in the stackframes container.
    *
    * @param object aFrame
    *        The stack frame to label.
    */
   getFrameTitle: function(aFrame) {
     if (aFrame.type == "call") {
       let c = aFrame.callee;
-      return (c.name || c.userDisplayName || c.displayName || "(anonymous)");
+      return (c.userDisplayName || c.displayName || c.name || "(anonymous)");
     }
     return "(" + aFrame.type + ")";
   },
 
   /**
    * Constructs a scope label based on its environment.
    *
    * @param object aEnv
@@ -697,17 +697,17 @@ let StackFrameUtils = {
     switch (aEnv.type) {
       case "with":
       case "object":
         label += " [" + aEnv.object.class + "]";
         break;
       case "function":
         let f = aEnv.function;
         label += " [" +
-          (f.name || f.userDisplayName || f.displayName || "(anonymous)") +
+          (f.userDisplayName || f.displayName || f.name || "(anonymous)") +
         "]";
         break;
     }
     return label;
   }
 };
 
 /**
--- a/browser/devtools/inspector/test/browser_inspector_scrolling.js
+++ b/browser/devtools/inspector/test/browser_inspector_scrolling.js
@@ -38,16 +38,18 @@ function inspectNode(aInspector)
     inspector.highlighter.unlock();
     inspector.selection.setNode(div, "");
   });
 }
 
 function performScrollingTest()
 {
   executeSoon(function() {
+    // FIXME: this will fail on retina displays. EventUtils will only scroll
+    // 25px down instead of 50.
     EventUtils.synthesizeWheel(div, 10, 10,
       { deltaY: 50.0, deltaMode: WheelEvent.DOM_DELTA_PIXEL },
       iframe.contentWindow);
   });
 
   gBrowser.selectedBrowser.addEventListener("scroll", function() {
     gBrowser.selectedBrowser.removeEventListener("scroll", arguments.callee,
       false);
--- a/browser/devtools/markupview/markup-view.css
+++ b/browser/devtools/markupview/markup-view.css
@@ -1,47 +1,84 @@
 /* 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/. */
 
-ul {
+#root-wrapper {
+  overflow: hidden;
+  min-width: 250px;
+}
+
+.children {
   list-style: none;
+  padding: 0;
+  margin: 0;
+}
+
+.child {
+  margin-left: -1000em;
+  padding-left: 1001em;
 }
 
-ul.children:not([expanded]) {
+.tag-line {
+  min-height: 1.4em;
+  line-height: 1.4em;
+  position: relative;
+}
+
+/* Children are indented thanks to their parent's left padding, that means they
+ * are not stretching from edge to edge, which is what we want.
+ * So we insert a pseudo-element and make sure it covers the whole "line" */
+.tag-line .highlighter {
+    content: "";
+    position: absolute;
+    left: -1000em;
+    right: 0;
+    height: 100%;
+    z-index: -1;
+}
+
+.expander {
+  display: inline-block;
+  margin-left: -14px;
+  vertical-align: middle;
+}
+
+.child.collapsed .child {
   display: none;
 }
 
-.codebox {
-  display: inline-block;
+.child > .tag-line:first-child .close {
+  display: none;
+}
+
+.child.collapsed > .tag-line:first-child .close {
+  display: inline;
+}
+
+.child.collapsed > .tag-line ~ .tag-line {
+  display: none;
+}
+
+.child.collapsed .close {
+  display: inline;
 }
 
 .newattr {
   display: inline-block;
   width: 1em;
   height: 1ex;
   margin-right: -1em;
+  padding: 1px 0;
 }
 
 .newattr:focus {
   margin-right: 0;
 }
 
-.closing-bracket {
-  pointer-events: none;
-}
-
-.summary {
-  cursor: pointer;
-}
-
-.summary[expanded] {
-  display: none;
-}
-
 /* Preview */
 
 #previewbar {
   position: fixed;
   top: 0;
   right: 0;
   width: 90px;
   background: black;
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -3,29 +3,26 @@
 /* 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/. */
 
 const {Cc, Cu, Ci} = require("chrome");
 
 // Page size for pageup/pagedown
 const PAGE_SIZE = 10;
-
 const PREVIEW_AREA = 700;
 const DEFAULT_MAX_CHILDREN = 100;
 
-let {UndoStack} = require("devtools/shared/undo");
-let EventEmitter = require("devtools/shared/event-emitter");
-let {editableField, InplaceEditor} = require("devtools/shared/inplace-editor");
-let promise = require("sdk/core/promise");
+const {UndoStack} = require("devtools/shared/undo");
+const {editableField, InplaceEditor} = require("devtools/shared/inplace-editor");
+const promise = require("sdk/core/promise");
 
 Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
 Cu.import("resource://gre/modules/devtools/Templater.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 loader.lazyGetter(this, "DOMParser", function() {
  return Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
 });
 loader.lazyGetter(this, "AutocompletePopup", () => require("devtools/shared/autocomplete-popup").AutocompletePopup);
 
 /**
  * Vocabulary for the purposes of this file:
@@ -187,17 +184,18 @@ MarkupView.prototype = {
         } else {
           let parent = this._selectionWalker().parentNode();
           if (parent) {
             this.navigate(parent.container);
           }
         }
         break;
       case Ci.nsIDOMKeyEvent.DOM_VK_RIGHT:
-        if (!this._selectedContainer.expanded) {
+        if (!this._selectedContainer.expanded &&
+            this._selectedContainer.hasChildren) {
           this._expandContainer(this._selectedContainer);
         } else {
           let next = this._selectionWalker().nextNode();
           if (next) {
             this.navigate(next.container);
           }
         }
         break;
@@ -360,17 +358,17 @@ MarkupView.prototype = {
   {
     for (let mutation of aMutations) {
       let type = mutation.type;
       let target = mutation.target;
 
       if (mutation.type === "documentUnload") {
         // Treat this as a childList change of the child (maybe the protocol
         // should do this).
-        type = "childList"
+        type = "childList";
         target = mutation.targetParent;
         if (!target) {
           continue;
         }
       }
 
       let container = this._containers.get(target);
       if (!container) {
@@ -385,17 +383,16 @@ MarkupView.prototype = {
         this._updateChildren(container);
       }
     }
     this._waitForChildren().then(() => {
       this._inspector.emit("markupmutation");
     });
   },
 
-
   /**
    * Make sure the given node's parents are expanded and the
    * node is scrolled on to screen.
    */
   showNode: function MT_showNode(aNode, centered)
   {
     let container = this.importNode(aNode);
     let parent = aNode;
@@ -414,17 +411,17 @@ MarkupView.prototype = {
 
   /**
    * Expand the container's children.
    */
   _expandContainer: function MT__expandContainer(aContainer)
   {
     return this._updateChildren(aContainer, true).then(() => {
       aContainer.expanded = true;
-    })
+    });
   },
 
   /**
    * Expand the node's children.
    */
   expandNode: function MT_expandNode(aNode)
   {
     let container = this._containers.get(aNode);
@@ -802,90 +799,83 @@ MarkupView.prototype = {
     let win = this._frame.contentWindow;
     this._previewBar.classList.add("hide");
     win.clearTimeout(this._resizePreviewTimeout);
 
     win.setTimeout(function() {
       this._updatePreview();
       this._previewBar.classList.remove("hide");
     }.bind(this), 1000);
-  },
-
+  }
 };
 
 
 /**
  * The main structure for storing a document node in the markup
  * tree.  Manages creation of the editor for the node and
  * a <ul> for placing child elements, and expansion/collapsing
  * of the element.
- *
+ * 
  * @param MarkupView aMarkupView
  *        The markup view that owns this container.
  * @param DOMNode aNode
  *        The node to display.
  */
-function MarkupContainer(aMarkupView, aNode)
-{
+function MarkupContainer(aMarkupView, aNode) {
   this.markup = aMarkupView;
   this.doc = this.markup.doc;
   this.undo = this.markup.undo;
   this.node = aNode;
 
   if (aNode.nodeType == Ci.nsIDOMNode.TEXT_NODE) {
     this.editor = new TextEditor(this, aNode, "text");
   } else if (aNode.nodeType == Ci.nsIDOMNode.COMMENT_NODE) {
     this.editor = new TextEditor(this, aNode, "comment");
   } else if (aNode.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
     this.editor = new ElementEditor(this, aNode);
   } else if (aNode.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE) {
     this.editor = new DoctypeEditor(this, aNode);
   } else {
-    this.editor = new GenericEditor(this.markup, aNode);
+    this.editor = new GenericEditor(this, aNode);
   }
 
   // The template will fill the following properties
   this.elt = null;
   this.expander = null;
-  this.codeBox = null;
+  this.highlighter = null;
+  this.tagLine = null;
   this.children = null;
   this.markup.template("container", this);
   this.elt.container = this;
   this.children.container = this;
 
-  this.expander.addEventListener("click", function() {
-    this.markup.navigate(this);
-
-    this.markup.setNodeExpanded(this.node, !this.expanded);
-  }.bind(this));
-
-  this.codeBox.insertBefore(this.editor.elt, this.children);
-
-  this.editor.elt.addEventListener("mousedown", function(evt) {
-    this.markup.navigate(this);
-  }.bind(this), false);
+  // Expanding/collapsing the node on dblclick of the whole tag-line element
+  this._onToggle = this._onToggle.bind(this);
+  this.elt.addEventListener("dblclick", this._onToggle, false);
+  this.expander.addEventListener("click", this._onToggle, false);
 
-  if (this.editor.summaryElt) {
-    this.editor.summaryElt.addEventListener("click", function(evt) {
-      this.markup.navigate(this);
-      this.markup.expandNode(this.node);
-    }.bind(this), false);
-    this.codeBox.appendChild(this.editor.summaryElt);
-  }
+  // Dealing with the highlighting of the row via javascript rather than :hover
+  // This is to allow highlighting the closing tag-line as well as reusing the
+  // theme css classes (which wouldn't have been possible with a :hover pseudo)
+  this._onMouseOver = this._onMouseOver.bind(this);
+  this.elt.addEventListener("mouseover", this._onMouseOver, false);
 
-  if (this.editor.closeElt) {
-    this.editor.closeElt.addEventListener("mousedown", function(evt) {
-      this.markup.navigate(this);
-    }.bind(this), false);
-    this.codeBox.appendChild(this.editor.closeElt);
-  }
+  this._onMouseOut = this._onMouseOut.bind(this);
+  this.elt.addEventListener("mouseout", this._onMouseOut, false);
+
+  // Appending the editor element and attaching event listeners
+  this.tagLine.appendChild(this.editor.elt);
+
+  this.elt.addEventListener("mousedown", this._onMouseDown.bind(this), false);
 }
 
 MarkupContainer.prototype = {
-  toString: function() { return "[MarkupContainer for " + this.node + "]" },
+  toString: function() {
+    return "[MarkupContainer for " + this.node + "]";
+  },
 
   /**
    * True if the current node has children.  The MarkupView
    * will set this attribute for the MarkupContainer.
    */
   _hasChildren: false,
 
   get hasChildren() {
@@ -904,104 +894,170 @@ MarkupContainer.prototype = {
   parentContainer: function() {
     return this.elt.parentNode ? this.elt.parentNode.container : null;
   },
 
   /**
    * True if the node has been visually expanded in the tree.
    */
   get expanded() {
-    return this.children.hasAttribute("expanded");
+    return !this.elt.classList.contains("collapsed");
   },
 
   set expanded(aValue) {
-    if (aValue) {
+    if (aValue && this.elt.classList.contains("collapsed")) {
+      // Expanding a node means cloning its "inline" closing tag into a new
+      // tag-line that the user can interact with and showing the children.
+      if (this.editor instanceof ElementEditor) {
+        let closingTag = this.elt.querySelector(".close");
+        if (closingTag) {
+          if (!this.closeTagLine) {
+            let line = this.markup.doc.createElement("div");
+            line.classList.add("tag-line");
+
+            let highlighter = this.markup.doc.createElement("div");
+            highlighter.classList.add("highlighter");
+            line.appendChild(highlighter);
+
+            line.appendChild(closingTag.cloneNode(true));
+            line.addEventListener("mouseover", this._onMouseOver, false);
+            line.addEventListener("mouseout", this._onMouseOut, false);
+
+            this.closeTagLine = line;
+          }
+          this.elt.appendChild(this.closeTagLine);
+        }
+      }
+      this.elt.classList.remove("collapsed");
       this.expander.setAttribute("open", "");
-      this.children.setAttribute("expanded", "");
-      if (this.editor.summaryElt) {
-        this.editor.summaryElt.setAttribute("expanded", "");
+      this.highlighted = false;
+    } else if (!aValue) {
+      if (this.editor instanceof ElementEditor && this.closeTagLine) {
+        this.elt.removeChild(this.closeTagLine);
+      }
+      this.elt.classList.add("collapsed");
+      this.expander.removeAttribute("open");
+    }
+  },
+
+  _onToggle: function(event) {
+    this.markup.navigate(this);
+    if(this.hasChildren) {
+      this.markup.setNodeExpanded(this.node, !this.expanded);
+    }
+    event.stopPropagation();
+  },
+
+  _onMouseOver: function(event) {
+    this.highlighted = true;
+    event.stopPropagation();
+  },
+
+  _onMouseOut: function(event) {
+    this.highlighted = false;
+    event.stopPropagation();
+  },
+
+  _onMouseDown: function(event) {
+    this.highlighted = false;
+    this.markup.navigate(this);
+    event.stopPropagation();
+  },
+
+  _highlighted: false,
+
+  /**
+   * Highlight the currently hovered tag + its closing tag if necessary
+   * (that is if the tag is expanded)
+   */
+  set highlighted(aValue) {
+    this._highlighted = aValue;
+    if (aValue) {
+      if (!this.selected) {
+        this.highlighter.classList.add("theme-bg-darker");
+      }
+      if (this.closeTagLine) {
+        this.closeTagLine.querySelector(".highlighter").classList.add("theme-bg-darker");
       }
     } else {
-      this.expander.removeAttribute("open");
-      this.children.removeAttribute("expanded");
-      if (this.editor.summaryElt) {
-        this.editor.summaryElt.removeAttribute("expanded");
+      this.highlighter.classList.remove("theme-bg-darker");
+      if (this.closeTagLine) {
+        this.closeTagLine.querySelector(".highlighter").classList.remove("theme-bg-darker");
       }
     }
   },
 
   /**
    * True if the container is visible in the markup tree.
    */
-  get visible()
-  {
+  get visible() {
     return this.elt.getBoundingClientRect().height > 0;
   },
 
   /**
    * True if the container is currently selected.
    */
   _selected: false,
 
   get selected() {
     return this._selected;
   },
 
   set selected(aValue) {
     this._selected = aValue;
     this.editor.selected = aValue;
     if (this._selected) {
-      this.editor.elt.classList.add("theme-selected");
-      if (this.editor.closeElt) {
-        this.editor.closeElt.classList.add("theme-selected");
-      }
+      this.tagLine.setAttribute("selected", "");
+      this.highlighter.classList.add("theme-selected");
     } else {
-      this.editor.elt.classList.remove("theme-selected");
-      if (this.editor.closeElt) {
-        this.editor.closeElt.classList.remove("theme-selected");
-      }
+      this.tagLine.removeAttribute("selected");
+      this.highlighter.classList.remove("theme-selected");
     }
   },
 
   /**
    * Update the container's editor to the current state of the
    * viewed node.
    */
-  update: function MC_update()
-  {
+  update: function() {
     if (this.editor.update) {
       this.editor.update();
     }
   },
 
   /**
    * Try to put keyboard focus on the current editor.
    */
-  focus: function MC_focus()
-  {
+  focus: function() {
     let focusable = this.editor.elt.querySelector("[tabindex]");
     if (focusable) {
       focusable.focus();
     }
-  },
-}
+  }
+};
+
 
 /**
  * Dummy container node used for the root document element.
  */
-function RootContainer(aMarkupView, aNode)
-{
+function RootContainer(aMarkupView, aNode) {
   this.doc = aMarkupView.doc;
   this.elt = this.doc.createElement("ul");
   this.elt.container = this;
   this.children = this.elt;
   this.node = aNode;
   this.toString = function() { return "[root container]"}
 }
 
+RootContainer.prototype = {
+  hasChildren: true,
+  expanded: true,
+  update: function() {}
+};
+
 /**
  * Creates an editor for simple nodes.
  */
 function GenericEditor(aContainer, aNode)
 {
   this.elt = aContainer.doc.createElement("span");
   this.elt.className = "editor";
   this.elt.textContent = aNode.nodeName;
@@ -1112,37 +1168,29 @@ function ElementEditor(aContainer, aNode
 {
   this.doc = aContainer.doc;
   this.undo = aContainer.undo;
   this.template = aContainer.markup.template.bind(aContainer.markup);
   this.container = aContainer;
   this.markup = this.container.markup;
   this.node = aNode;
 
-  this.attrs = { };
+  this.attrs = {};
 
   // The templates will fill the following properties
   this.elt = null;
   this.tag = null;
+  this.closeTag = null;
   this.attrList = null;
   this.newAttr = null;
-  this.summaryElt = null;
   this.closeElt = null;
 
   // Create the main editor
   this.template("element", this);
 
-  if (this.node.hasChildren) {
-    // Create the summary placeholder
-    this.template("elementContentSummary", this);
-  }
-
-  // Create the closing tag
-  this.template("elementClose", this);
-
   this.rawNode = aNode.rawNode();
 
   // Make the tag name editable (unless this is a remote node or
   // a document element)
   if (this.rawNode && !aNode.isDocumentElement) {
     this.tag.setAttribute("tabindex", "0");
     editableField({
       element: this.tag,
@@ -1333,17 +1381,16 @@ ElementEditor.prototype = {
    *        user put them.
    */
   _applyAttributes: function EE__applyAttributes(aValue, aAttrNode, aDoMods, aUndoMods)
   {
     let attrs = parseAttributeValues(aValue, this.doc);
     for (let attr of attrs) {
       // Create an attribute editor next to the current attribute if needed.
       this._createAttribute(attr, aAttrNode ? aAttrNode.nextSibling : null);
-
       this._saveAttribute(attr.name, aUndoMods);
       aDoMods.setAttribute(attr.name, attr.value);
     }
   },
 
   /**
    * Saves the current state of the given attribute into an attribute
    * modification list.
@@ -1409,24 +1456,16 @@ ElementEditor.prototype = {
         swapNodes(newElt, this.rawNode);
         this.markup.setNodeExpanded(this.node, newContainer.expanded);
         if (newContainer.selected) {
           this.markup.navigate(this.container);
         }
       });
     }).then(null, console.error);
   }
-}
-
-
-
-RootContainer.prototype = {
-  hasChildren: true,
-  expanded: true,
-  update: function RC_update() {}
 };
 
 function nodeDocument(node) {
   return node.ownerDocument || (node.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE ? node : null);
 }
 
 /**
  * Parse attribute names and values from a string.
@@ -1434,17 +1473,16 @@ function nodeDocument(node) {
  * @param  {String} attr
  *         The input string for which names/values are to be parsed.
  * @param  {HTMLDocument} doc
  *         A document that can be used to test valid attributes.
  * @return {Array}
  *         An array of attribute names and their values.
  */
 function parseAttributeValues(attr, doc) {
-
   attr = attr.trim();
 
   // Handle bad user inputs by appending a " or ' if it fails to parse without them.
   let el = DOMParser.parseFromString("<div " + attr + "></div>", "text/html").body.childNodes[0] ||
            DOMParser.parseFromString("<div " + attr + "\"></div>", "text/html").body.childNodes[0] ||
            DOMParser.parseFromString("<div " + attr + "'></div>", "text/html").body.childNodes[0];
   let div = doc.createElement("div");
 
@@ -1461,24 +1499,11 @@ function parseAttributeValues(attr, doc)
     }
     catch(e) { }
   }
 
   // Attributes return from DOMParser in reverse order from how they are entered.
   return attributes.reverse();
 }
 
-/**
- * A tree walker filter for avoiding empty whitespace text nodes.
- */
-function whitespaceTextFilter(aNode)
-{
-    if (aNode.nodeType == Ci.nsIDOMNode.TEXT_NODE &&
-        !/[^\s]/.exec(aNode.nodeValue)) {
-      return Ci.nsIDOMNodeFilter.FILTER_SKIP;
-    } else {
-      return Ci.nsIDOMNodeFilter.FILTER_ACCEPT;
-    }
-}
-
 loader.lazyGetter(MarkupView.prototype, "strings", () => Services.strings.createBundle(
   "chrome://browser/locale/devtools/inspector.properties"
 ));
--- a/browser/devtools/markupview/markup-view.xhtml
+++ b/browser/devtools/markupview/markup-view.xhtml
@@ -10,38 +10,41 @@
   <link rel="stylesheet" href="chrome://browser/content/devtools/markup-view.css" type="text/css"/>
   <link rel="stylesheet" href="chrome://browser/skin/devtools/markup-view.css" type="text/css"/>
   <link rel="stylesheet" href="chrome://browser/skin/devtools/common.css" type="text/css"/>
 
   <script type="application/javascript;version=1.8" src="theme-switching.js"/>
 
 </head>
 <body class="theme-body devtools-monospace" role="application">
-  <div id="root"></div>
+  <div id="root-wrapper">
+    <div id="root"></div>
+  </div>
   <div id="templates" style="display:none">
-    <ul>
-      <li id="template-container" save="${elt}" class="container"><span save="${codeBox}" class="codebox"><span save="${expander}" class="theme-twisty expander"></span><ul save="${children}" class="children"></ul></span></li>
+
+    <ul class="children">
+      <li id="template-container" save="${elt}" class="child collapsed">
+        <div save="${tagLine}" class="tag-line"><span save="${highlighter}" class="highlighter"></span><span save="${expander}" class="theme-twisty expander"></span></div>
+        <ul save="${children}" class="children"></ul>
+      </li>
 
       <li id="template-more-nodes" class="more-nodes devtools-class-comment" save="${elt}"><span>${showing}</span> <button href="#" onclick="${allButtonClick}">${showAll}</button></li>
     </ul>
 
-    <span id="template-element" save="${elt}" class="editor"><span>&lt;</span><span save="${tag}" class="tagname theme-fg-color3"></span><span save="${attrList}"></span><span save="${newAttr}" class="newattr" tabindex="0"></span><span class="closing-bracket">&gt;</span></span>
+    <span id="template-element" save="${elt}" class="editor"><span class="open">&lt;<span save="${tag}" class="tag theme-fg-color3" tabindex="0"></span><span save="${attrList}"></span><span save="${newAttr}" class="newattr" tabindex="0"></span>&gt;</span><span class="close">&lt;/<span save="${closeTag}" class="tag theme-fg-color3"></span>&gt;</span></span>
 
-    <span id="template-attribute" save="${attr}" data-attr="${attrName}" class="attreditor" style="display:none"> <span class="editable" save="${inner}" tabindex="0"><span save="${name}" class="attrname theme-fg-color2"></span>=&quot;<span save="${val}" class="attrvalue theme-fg-color6"></span>&quot;</span></span>
+    <span id="template-attribute" save="${attr}" data-attr="${attrName}" class="attreditor" style="display:none"> <span class="editable" save="${inner}" tabindex="0"><span save="${name}" class="attr-name theme-fg-color2"></span>=&quot;<span save="${val}" class="attr-value theme-fg-color6"></span>&quot;</span></span>
 
     <span id="template-text" save="${elt}" class="editor text">
       <pre save="${value}" style="display:inline-block;" tabindex="0"></pre>
     </span>
 
-    <span id="template-comment" save="${elt}" class="editor comment theme-comment">
-      <span>&lt;!--</span><pre save="${value}" style="display:inline-block;" tabindex="0"></pre><span>--&gt;</span>
-    </span>
+    <span id="template-comment" save="${elt}" class="editor comment theme-comment"><span>&lt;!--</span><pre save="${value}" style="display:inline-block;" tabindex="0"></pre><span>--&gt;</span></span>
 
-    <span id="template-elementContentSummary" save="${summaryElt}" class="summary"> … </span>
+    <!-- span id="template-elementClose" save="${closeElt}">&lt;/<span save="${closeTag}" class="tagname theme-fg-color3"></span>&gt;</span -->
 
-    <span id="template-elementClose" save="${closeElt}">&lt;/<span save="${closeTag}" class="tagname theme-fg-color3"></span>&gt;</span>
-   </div>
-   <div id="previewbar" class="disabled">
+  </div>
+  <div id="previewbar" class="disabled">
      <div id="preview"/>
      <div id="viewbox"/>
-   </div>
+  </div>
 </body>
 </html>
--- a/browser/devtools/markupview/test/browser_inspector_markup_navigation.js
+++ b/browser/devtools/markupview/test/browser_inspector_markup_navigation.js
@@ -29,17 +29,16 @@ function test() {
     ["down" , "node7"],
     ["right", "node7"],
     ["down", "*text*"],
     ["down", "node8"],
     ["left", "node7"],
     ["left", "node7"],
     ["right", "node7"],
     ["right", "*text*"],
-    ["right", "*text*"],
     ["down", "node8"],
     ["right", "node8"],
     ["left", "node8"],
     ["down", "node9"],
     ["down", "node10"],
     ["down", "node11"],
     ["down", "node12"],
     ["right", "node12"],
--- a/browser/devtools/shared/inplace-editor.js
+++ b/browser/devtools/shared/inplace-editor.js
@@ -200,20 +200,19 @@ function InplaceEditor(aOptions, aEvent)
 
   this.input.addEventListener("blur", this._onBlur, false);
   this.input.addEventListener("keypress", this._onKeyPress, false);
   this.input.addEventListener("input", this._onInput, false);
   this.input.addEventListener("mousedown", function(aEvt) {
                                              aEvt.stopPropagation();
                                            }, false);
 
-  this.warning = aOptions.warning;
   this.validate = aOptions.validate;
 
-  if (this.warning && this.validate) {
+  if (this.validate) {
     this.input.addEventListener("keyup", this._onKeyup, false);
   }
 
   if (aOptions.start) {
     aOptions.start(this, aEvent);
   }
 
   EventEmitter.decorate(this);
@@ -249,25 +248,25 @@ InplaceEditor.prototype = {
     this.input.removeEventListener("keypress", this._onKeyPress, false);
     this.input.removeEventListener("keyup", this._onKeyup, false);
     this.input.removeEventListener("oninput", this._onInput, false);
     this._stopAutosize();
 
     this.elt.style.display = this.originalDisplay;
     this.elt.focus();
 
-    if (this.destroy) {
-      this.destroy();
-    }
-
     this.elt.parentNode.removeChild(this.input);
     this.input = null;
 
     delete this.elt.inplaceEditor;
     delete this.elt;
+
+    if (this.destroy) {
+      this.destroy();
+    }
   },
 
   /**
    * Keeps the editor close to the size of its input string.  This is pretty
    * crappy, suggestions for improvement welcome.
    */
   _autosize: function InplaceEditor_autosize()
   {
@@ -347,17 +346,17 @@ InplaceEditor.prototype = {
                                            selectionEnd);
 
     if (!newValue) {
       return false;
     }
 
     this.input.value = newValue.value;
     this.input.setSelectionRange(newValue.start, newValue.end);
-    this.warning.hidden = this.validate(this.input.value);
+    this._doValidation();
 
     return true;
   },
 
   /**
    * Increment the property value based on the property type.
    *
    * @param {string} value
@@ -763,18 +762,20 @@ InplaceEditor.prototype = {
       let input = this.input;
       let pre = input.value.slice(0, input.selectionStart);
       let post = input.value.slice(input.selectionEnd, input.value.length);
       let item = this.popup.selectedItem;
       let toComplete = item.label.slice(item.preLabel.length);
       input.value = pre + toComplete + post;
       input.setSelectionRange(pre.length, pre.length + toComplete.length);
       this._updateSize();
+
       // This emit is mainly for the purpose of making the test flow simpler.
       this.emit("after-suggest");
+      this._doValidation();
     }
 
     if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_BACK_SPACE ||
         aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_DELETE ||
         aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_LEFT ||
         aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RIGHT) {
       if (this.popup && this.popup.isOpen) {
         this.popup.hidePopup();
@@ -850,43 +851,49 @@ InplaceEditor.prototype = {
       aEvent.preventDefault();
     }
   },
 
   /**
    * Handle the input field's keyup event.
    */
   _onKeyup: function(aEvent) {
-    // Validate the entered value.
-    this.warning.hidden = this.validate(this.input.value);
     this._applied = false;
   },
 
   /**
    * Handle changes to the input text.
    */
   _onInput: function InplaceEditor_onInput(aEvent)
   {
     // Validate the entered value.
-    if (this.warning && this.validate) {
-      this.warning.hidden = this.validate(this.input.value);
-    }
+    this._doValidation();
 
     // Update size if we're autosizing.
     if (this._measurement) {
       this._updateSize();
     }
 
     // Call the user's change handler if available.
     if (this.change) {
       this.change(this.input.value.trim());
     }
   },
 
   /**
+   * Fire validation callback with current input
+   */
+  _doValidation: function()
+  {
+    if (this.validate && this.input) {
+      this.validate(this.input.value);
+    }
+  },
+
+  /**
    * Handles displaying suggestions based on the current input.
    */
   _maybeSuggestCompletion: function() {
     // Since we are calling this method from a keypress event handler, the
     // |input.value| does not include currently typed character. Thus we perform
     // this method async.
     this.doc.defaultView.setTimeout(() => {
       if (this._preventSuggestions) {
@@ -975,16 +982,17 @@ InplaceEditor.prototype = {
       if (finalList.length > 1) {
         this.popup.setItems(finalList);
         this.popup.openPopup(this.input);
       } else {
         this.popup.hidePopup();
       }
       // This emit is mainly for the purpose of making the test flow simpler.
       this.emit("after-suggest");
+      this._doValidation();
     }, 0);
   }
 };
 
 /**
  * Copy text-related styles from one element to another.
  */
 function copyTextStyles(aFrom, aTo)
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -520,31 +520,36 @@ Rule.prototype = {
    *
    * @param {string} [aName]
    *        A text property name (such as "background" or "border-top") used
    *        when calling from setPropertyValue & setPropertyName to signify
    *        that the property should be saved in store.userProperties.
    */
   applyProperties: function Rule_applyProperties(aModifications, aName)
   {
+    this.elementStyle.markOverriddenAll();
+
     if (!aModifications) {
       aModifications = this.style.startModifyingProperties();
     }
     let disabledProps = [];
     let store = this.elementStyle.store;
 
     for (let prop of this.textProps) {
       if (!prop.enabled) {
         disabledProps.push({
           name: prop.name,
           value: prop.value,
           priority: prop.priority
         });
         continue;
       }
+      if (prop.value.trim() === "") {
+        continue;
+      }
 
       aModifications.setProperty(prop.name, prop.value, prop.priority);
 
       prop.updateComputed();
     }
 
     // Store disabled properties in the disabled store.
     let disabled = this.elementStyle.store.disabled;
@@ -624,16 +629,17 @@ Rule.prototype = {
    * @param {string} aPriority
    *        The property's priority (either "important" or an empty string).
    */
   setPropertyValue: function Rule_setPropertyValue(aProperty, aValue, aPriority)
   {
     if (aValue === aProperty.value && aPriority === aProperty.priority) {
       return;
     }
+
     aProperty.value = aValue;
     aProperty.priority = aPriority;
     this.applyProperties(null, aProperty.name);
   },
 
   /**
    * Disables or enables given TextProperty.
    */
@@ -841,16 +847,55 @@ Rule.prototype = {
     // value.
     if (match.prop) {
       match.prop.set(aNewProp);
       return true;
     }
 
     return false;
   },
+
+  /**
+   * Jump between editable properties in the UI.  Will begin editing the next
+   * name, if possible.  If this is the last element in the set, then begin
+   * editing the previous value.  If this is the *only* element in the set,
+   * then settle for focusing the new property editor.
+   *
+   * @param {TextProperty} aTextProperty
+   *        The text property that will be left to focus on a sibling.
+   *
+   */
+  editClosestTextProperty: function Rule__editClosestTextProperty(aTextProperty)
+  {
+    let index = this.textProps.indexOf(aTextProperty);
+    let previous = false;
+
+    // If this is the last element, move to the previous instead of next
+    if (index === this.textProps.length - 1) {
+      index = index - 1;
+      previous = true;
+    }
+    else {
+      index = index + 1;
+    }
+
+    let nextProp = this.textProps[index];
+
+    // If possible, begin editing the next name or previous value.
+    // Otherwise, settle for focusing the new property element.
+    if (nextProp) {
+      if (previous) {
+        nextProp.editor.valueSpan.click();
+      } else {
+        nextProp.editor.nameSpan.click();
+      }
+    } else {
+      aTextProperty.rule.editor.closeBrace.focus();
+    }
+  }
 };
 
 /**
  * A single property in a rule's cssText.
  *
  * @param {Rule} aRule
  *        The rule this TextProperty came from.
  * @param {string} aName
@@ -1268,17 +1313,17 @@ CssRuleView.prototype = {
           this.togglePseudoElementVisibility(!this.showPseudoElements);
         }, false);
 
         div.insertBefore(twisty, div.firstChild);
         this.element.appendChild(div);
       }
 
       if (!rule.editor) {
-        new RuleEditor(this, rule);
+        rule.editor = new RuleEditor(this, rule);
       }
 
       this.element.appendChild(rule.editor.element);
     }
 
     this.togglePseudoElementVisibility(this.showPseudoElements);
   },
 
@@ -1328,17 +1373,16 @@ CssRuleView.prototype = {
  *        The Rule object we're editing.
  * @constructor
  */
 function RuleEditor(aRuleView, aRule)
 {
   this.ruleView = aRuleView;
   this.doc = this.ruleView.doc;
   this.rule = aRule;
-  this.rule.editor = this;
 
   this._onNewProperty = this._onNewProperty.bind(this);
   this._newPropertyDestroy = this._newPropertyDestroy.bind(this);
 
   this._create();
 }
 
 RuleEditor.prototype = {
@@ -1392,27 +1436,16 @@ RuleEditor.prototype = {
       let selection = this.doc.defaultView.getSelection();
       if (selection.isCollapsed) {
         this.newProperty();
       }
     }.bind(this), false);
 
     this.element.addEventListener("mousedown", function() {
       this.doc.defaultView.focus();
-
-      let editorNodes =
-        this.doc.querySelectorAll(".styleinspector-propertyeditor");
-
-      if (editorNodes) {
-        for (let node of editorNodes) {
-          if (node.inplaceEditor) {
-            node.inplaceEditor._clear();
-          }
-        }
-      }
     }.bind(this), false);
 
     this.propertyList = createChild(code, "ul", {
       class: "ruleview-propertylist"
     });
 
     this.populate();
 
@@ -1461,18 +1494,18 @@ RuleEditor.prototype = {
           class: cls,
           textContent: selector
         });
       });
     }
 
     for (let prop of this.rule.textProps) {
       if (!prop.editor) {
-        new TextPropertyEditor(this, prop);
-        this.propertyList.appendChild(prop.editor.element);
+        let editor = new TextPropertyEditor(this, prop);
+        this.propertyList.appendChild(editor.element);
       }
     }
   },
 
   /**
    * Programatically add a new property to the rule.
    *
    * @param {string} aName
@@ -1576,34 +1609,39 @@ RuleEditor.prototype = {
 function TextPropertyEditor(aRuleEditor, aProperty)
 {
   this.ruleEditor = aRuleEditor;
   this.doc = this.ruleEditor.doc;
   this.popup = this.ruleEditor.ruleView.popup;
   this.prop = aProperty;
   this.prop.editor = this;
   this.browserWindow = this.doc.defaultView.top;
+  this.removeOnRevert = this.prop.value === "";
 
   let sheet = this.prop.rule.sheet;
   let href = sheet ? (sheet.href || sheet.nodeHref) : null;
   if (href) {
     this.sheetURI = IOService.newURI(href, null, null);
   }
 
   this._onEnableClicked = this._onEnableClicked.bind(this);
   this._onExpandClicked = this._onExpandClicked.bind(this);
   this._onStartEditing = this._onStartEditing.bind(this);
   this._onNameDone = this._onNameDone.bind(this);
   this._onValueDone = this._onValueDone.bind(this);
+  this._onValidate = throttle(this._livePreview, 10, this, this.browserWindow);
 
   this._create();
   this.update();
 }
 
 TextPropertyEditor.prototype = {
+  /**
+   * Boolean indicating if the name or value is being currently edited.
+   */
   get editing() {
     return !!(this.nameSpan.inplaceEditor || this.valueSpan.inplaceEditor);
   },
 
   /**
    * Create the property editor's DOM.
    */
   _create: function TextPropertyEditor_create()
@@ -1622,55 +1660,56 @@ TextPropertyEditor.prototype = {
     this.expander = createChild(this.element, "span", {
       class: "ruleview-expander theme-twisty"
     });
     this.expander.addEventListener("click", this._onExpandClicked, true);
 
     this.nameContainer = createChild(this.element, "span", {
       class: "ruleview-namecontainer"
     });
-    this.nameContainer.addEventListener("click", function(aEvent) {
+    this.nameContainer.addEventListener("click", (aEvent) => {
       // Clicks within the name shouldn't propagate any further.
       aEvent.stopPropagation();
       if (aEvent.target === propertyContainer) {
         this.nameSpan.click();
       }
-    }.bind(this), false);
+    }, false);
 
     // Property name, editable when focused.  Property name
     // is committed when the editor is unfocused.
     this.nameSpan = createChild(this.nameContainer, "span", {
       class: "ruleview-propertyname theme-fg-color5",
       tabindex: "0",
     });
 
     editableField({
       start: this._onStartEditing,
       element: this.nameSpan,
       done: this._onNameDone,
+      destroy: this.update.bind(this),
       advanceChars: ':',
       contentType: InplaceEditor.CONTENT_TYPES.CSS_PROPERTY,
       popup: this.popup
     });
 
     appendText(this.nameContainer, ": ");
 
     // Create a span that will hold the property and semicolon.
     // Use this span to create a slightly larger click target
     // for the value.
     let propertyContainer = createChild(this.element, "span", {
       class: "ruleview-propertycontainer"
     });
-    propertyContainer.addEventListener("click", function(aEvent) {
+    propertyContainer.addEventListener("click", (aEvent) => {
       // Clicks within the value shouldn't propagate any further.
       aEvent.stopPropagation();
       if (aEvent.target === propertyContainer) {
         this.valueSpan.click();
       }
-    }.bind(this), false);
+    }, false);
 
     // Property value, editable when focused.  Changes to the
     // property value are applied as they are typed, and reverted
     // if the user presses escape.
     this.valueSpan = createChild(propertyContainer, "span", {
       class: "ruleview-propertyvalue theme-fg-color1",
       tabindex: "0",
     });
@@ -1679,33 +1718,33 @@ TextPropertyEditor.prototype = {
     // for restoring after pressing escape.
     this.committed = { name: this.prop.name,
                        value: this.prop.value,
                        priority: this.prop.priority };
 
     appendText(propertyContainer, ";");
 
     this.warning = createChild(this.element, "div", {
+      class: "ruleview-warning",
       hidden: "",
-      class: "ruleview-warning",
       title: CssLogic.l10n("rule.warning.title"),
     });
 
     // Holds the viewers for the computed properties.
     // will be populated in |_updateComputed|.
     this.computed = createChild(this.element, "ul", {
       class: "ruleview-computedlist",
     });
 
     editableField({
       start: this._onStartEditing,
       element: this.valueSpan,
       done: this._onValueDone,
-      validate: this._validate.bind(this),
-      warning: this.warning,
+      destroy: this.update.bind(this),
+      validate: this._onValidate,
       advanceChars: ';',
       contentType: InplaceEditor.CONTENT_TYPES.CSS_VALUE,
       property: this.prop,
       popup: this.popup
     });
   },
 
   /**
@@ -1746,32 +1785,33 @@ TextPropertyEditor.prototype = {
     if (this.prop.enabled) {
       this.enable.style.removeProperty("visibility");
       this.enable.setAttribute("checked", "");
     } else {
       this.enable.style.visibility = "visible";
       this.enable.removeAttribute("checked");
     }
 
-    if (this.prop.overridden && !this.editing) {
+    this.warning.hidden = this.editing || this.isValid();
+
+    if ((this.prop.overridden || !this.prop.enabled) && !this.editing) {
       this.element.classList.add("ruleview-overridden");
     } else {
       this.element.classList.remove("ruleview-overridden");
     }
 
     let name = this.prop.name;
     this.nameSpan.textContent = name;
 
     // Combine the property's value and priority into one string for
     // the value.
     let val = this.prop.value;
     if (this.prop.priority) {
       val += " !" + this.prop.priority;
     }
-
     // Treat URLs differently than other properties.
     // Allow the user to click a link to the resource and open it.
     let resourceURI = this.getResourceURI();
     if (resourceURI) {
       this.valueSpan.textContent = "";
 
       appendText(this.valueSpan, val.split(resourceURI)[0]);
 
@@ -1792,33 +1832,32 @@ TextPropertyEditor.prototype = {
 
       }, false);
 
       appendText(this.valueSpan, val.split(resourceURI)[1]);
     } else {
       this.valueSpan.textContent = val;
     }
 
-    this.warning.hidden = this._validate();
-
     let store = this.prop.rule.elementStyle.store;
     let propDirty = store.userProperties.contains(this.prop.rule.style, name);
     if (propDirty) {
       this.element.setAttribute("dirty", "");
     } else {
       this.element.removeAttribute("dirty");
     }
 
     // Populate the computed styles.
     this._updateComputed();
   },
 
   _onStartEditing: function TextPropertyEditor_onStartEditing()
   {
     this.element.classList.remove("ruleview-overridden");
+    this._livePreview(this.prop.value);
   },
 
   /**
    * Populate the list of computed styles.
    */
   _updateComputed: function TextPropertyEditor_updateComputed()
   {
     // Clear out existing viewers.
@@ -1901,29 +1940,23 @@ TextPropertyEditor.prototype = {
    *
    * @param {string} aValue
    *        The value contained in the editor.
    * @param {boolean} aCommit
    *        True if the change should be applied.
    */
   _onNameDone: function TextPropertyEditor_onNameDone(aValue, aCommit)
   {
-    if (!aCommit) {
-      if (this.prop.overridden) {
-        this.element.classList.add("ruleview-overridden");
+    if (aCommit) {
+      if (aValue.trim() === "") {
+        this.remove();
+      } else {
+        this.prop.setName(aValue);
       }
-
-      return;
     }
-    if (!aValue) {
-      this.prop.remove();
-      this.element.parentNode.removeChild(this.element);
-      return;
-    }
-    this.prop.setName(aValue);
   },
 
   /**
    * Pull priority (!important) out of the value provided by a
    * value editor.
    *
    * @param {string} aValue
    *        The value from the text editor.
@@ -1934,70 +1967,110 @@ TextPropertyEditor.prototype = {
     let pieces = aValue.split("!", 2);
     return {
       value: pieces[0].trim(),
       priority: (pieces.length > 1 ? pieces[1].trim() : "")
     };
   },
 
   /**
+   * Remove property from style and the editors from DOM.
+   * Begin editing next available property.
+   */
+  remove: function TextPropertyEditor_remove()
+  {
+    this.element.parentNode.removeChild(this.element);
+    this.ruleEditor.rule.editClosestTextProperty(this.prop);
+    this.prop.remove();
+  },
+
+  /**
    * Called when a value editor closes.  If the user pressed escape,
    * revert to the value this property had before editing.
    *
    * @param {string} aValue
    *        The value contained in the editor.
    * @param {bool} aCommit
    *        True if the change should be applied.
    */
    _onValueDone: function PropertyEditor_onValueDone(aValue, aCommit)
   {
     if (aCommit) {
       let val = this._parseValue(aValue);
-      this.prop.setValue(val.value, val.priority);
-      this.committed.value = this.prop.value;
-      this.committed.priority = this.prop.priority;
-      if (this.prop.overridden) {
-        this.element.classList.add("ruleview-overridden");
+      // Any property should be removed if has an empty value.
+      if (val.value.trim() === "") {
+        this.remove();
+      } else {
+        this.prop.setValue(val.value, val.priority);
+        this.removeOnRevert = false;
+        this.committed.value = this.prop.value;
+        this.committed.priority = this.prop.priority;
       }
     } else {
-      this.prop.setValue(this.committed.value, this.committed.priority);
+      // A new property should be removed when escape is pressed.
+      if (this.removeOnRevert) {
+        this.remove();
+      } else {
+        this.prop.setValue(this.committed.value, this.committed.priority);
+      }
     }
   },
 
   /**
-   * Validate this property.
+   * Live preview this property, without committing changes.
    *
    * @param {string} [aValue]
-   *        Override the actual property value used for validation without
-   *        applying property values e.g. validate as you type.
+   *        The value to set the current property to.
+   */
+  _livePreview: function TextPropertyEditor_livePreview(aValue)
+  {
+    // Since function call is throttled, we need to make sure we are still editing
+    if (!this.editing) {
+      return;
+    }
+
+    let val = this._parseValue(aValue);
+
+    // Live previewing the change without committing just yet, that'll be done in _onValueDone
+    // If it was not a valid value, apply an empty string to reset the live preview
+    this.ruleEditor.rule.setPropertyValue(this.prop, val.value, val.priority);
+  },
+
+  /**
+   * Validate this property. Does it make sense for this value to be assigned
+   * to this property name? This does not apply the property value
+   *
+   * @param {string} [aValue]
+   *        The property value used for validation.
+   *        Defaults to the current value for this.prop
    *
    * @return {bool} true if the property value is valid, false otherwise.
    */
-  _validate: function TextPropertyEditor_validate(aValue)
+  isValid: function TextPropertyEditor_isValid(aValue)
   {
     let name = this.prop.name;
     let value = typeof aValue == "undefined" ? this.prop.value : aValue;
     let val = this._parseValue(value);
 
     let style = this.doc.createElementNS(HTML_NS, "div").style;
     let prefs = Services.prefs;
 
     // We toggle output of errors whilst the user is typing a property value.
-    let prefVal = Services.prefs.getBoolPref("layout.css.report_errors");
+    let prefVal = prefs.getBoolPref("layout.css.report_errors");
     prefs.setBoolPref("layout.css.report_errors", false);
 
+    let validValue = false;
     try {
       style.setProperty(name, val.value, val.priority);
-      // Live previewing the change without committing yet just yet, that'll be done in _onValueDone
-      this.ruleEditor.rule.setPropertyValue(this.prop, val.value, val.priority);
+      validValue = style.getPropertyValue(name) !== "" || val.value === "";
     } finally {
       prefs.setBoolPref("layout.css.report_errors", prefVal);
     }
-    return !!style.getPropertyValue(name);
-  },
+    return validValue;
+  }
 };
 
 /**
  * Store of CSSStyleDeclarations mapped to properties that have been changed by
  * the user.
  */
 function UserProperties()
 {
@@ -2112,16 +2185,32 @@ function createMenuItem(aMenu, aAttribut
   item.setAttribute("accesskey", _strings.GetStringFromName(aAttributes.accesskey));
   item.addEventListener("command", aAttributes.command);
 
   aMenu.appendChild(item);
 
   return item;
 }
 
+
+function throttle(func, wait, scope, window) {
+  var timer = null;
+  return function() {
+    if(timer) {
+      window.clearTimeout(timer);
+    }
+    var args = arguments;
+    timer = window.setTimeout(function() {
+      timer = null;
+      func.apply(scope, args);
+    }, wait);
+  };
+}
+
+
 /**
  * Append a text node to an element.
  */
 function appendText(aParent, aText)
 {
   aParent.appendChild(aParent.ownerDocument.createTextNode(aText));
 }
 
--- a/browser/devtools/styleinspector/test/browser_ruleview_editor_changedvalues.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_editor_changedvalues.js
@@ -65,48 +65,86 @@ function testCreateNew()
         is(elementRuleEditor.rule.textProps.length,  1, "Should have created a new text property.");
         is(elementRuleEditor.propertyList.children.length, 1, "Should have created a property editor.");
         let textProp = elementRuleEditor.rule.textProps[0];
         is(aEditor, inplaceEditor(textProp.editor.valueSpan), "Should be editing the value span now.");
         aEditor.input.value = "#XYZ";
         waitForEditorBlur(aEditor, function() {
           promiseDone(expectRuleChange(elementRuleEditor.rule).then(() => {
             is(textProp.value, "#XYZ", "Text prop should have been changed.");
-            is(textProp.editor._validate(), false, "#XYZ should not be a valid entry");
-            testEditProperty();
+            is(textProp.editor.isValid(), false, "#XYZ should not be a valid entry");
+            testCreateNewEscape();
           }));
         });
         aEditor.input.blur();
       }));
     });
     EventUtils.synthesizeKey("VK_RETURN", {}, ruleWindow);
   });
 
   EventUtils.synthesizeMouse(elementRuleEditor.closeBrace, 1, 1,
                              { },
                              ruleWindow);
 }
 
+function testCreateNewEscape()
+{
+  // Create a new property.
+  let elementRuleEditor = ruleView.element.children[0]._ruleEditor;
+  waitForEditorFocus(elementRuleEditor.element, function onNewElement(aEditor) {
+    is(inplaceEditor(elementRuleEditor.newPropSpan), aEditor, "Next focused editor should be the new property editor.");
+    let input = aEditor.input;
+    input.value = "color";
+
+    waitForEditorFocus(elementRuleEditor.element, function onNewValue(aEditor) {
+      promiseDone(expectRuleChange(elementRuleEditor.rule).then(() => {
+        is(elementRuleEditor.rule.textProps.length,  2, "Should have created a new text property.");
+        is(elementRuleEditor.propertyList.children.length, 2, "Should have created a property editor.");
+        let textProp = elementRuleEditor.rule.textProps[1];
+        is(aEditor, inplaceEditor(textProp.editor.valueSpan), "Should be editing the value span now.");
+        aEditor.input.value = "red";
+        EventUtils.synthesizeKey("VK_ESCAPE", {}, ruleWindow);
+
+        // Make sure previous input is focused.
+        let focusedElement = inplaceEditor(elementRuleEditor.rule.textProps[0].editor.valueSpan).input;
+        is(focusedElement, focusedElement.ownerDocument.activeElement, "Correct element has focus");
+
+        EventUtils.synthesizeKey("VK_ESCAPE", {}, ruleWindow);
+
+        is(elementRuleEditor.rule.textProps.length,  1, "Should have removed the new text property.");
+        is(elementRuleEditor.propertyList.children.length, 1, "Should have removed the property editor.");
+
+        testEditProperty();
+      }));
+    });
+    EventUtils.synthesizeKey("VK_RETURN", {}, ruleWindow);
+  });
+
+  EventUtils.synthesizeMouse(elementRuleEditor.closeBrace, 1, 1,
+                             { },
+                             ruleWindow);
+}
+
 function testEditProperty()
 {
   let idRuleEditor = ruleView.element.children[1]._ruleEditor;
   let propEditor = idRuleEditor.rule.textProps[0].editor;
   waitForEditorFocus(propEditor.element, function onNewElement(aEditor) {
     is(inplaceEditor(propEditor.nameSpan), aEditor, "Next focused editor should be the name editor.");
     let input = aEditor.input;
     waitForEditorFocus(propEditor.element, function onNewName(aEditor) {
       promiseDone(expectRuleChange(idRuleEditor.rule).then(() => {
         input = aEditor.input;
         is(inplaceEditor(propEditor.valueSpan), aEditor, "Focus should have moved to the value.");
 
         waitForEditorBlur(aEditor, function() {
           promiseDone(expectRuleChange(idRuleEditor.rule).then(() => {
             let value = idRuleEditor.rule.domRule._rawStyle().getPropertyValue("border-color");
             is(value, "red", "border-color should have been set.");
-            is(propEditor._validate(), true, "red should be a valid entry");
+            is(propEditor.isValid(), true, "red should be a valid entry");
             finishTest();
           }));
         });
 
         for (let ch of "red;") {
           EventUtils.sendChar(ch, ruleWindow);
         }
       }));
--- a/browser/devtools/styleinspector/test/browser_ruleview_livepreview.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_livepreview.js
@@ -12,17 +12,24 @@ let inspector;
 
 // Format
 // {
 //   value : what to type in the field
 //   expected : expected computed style on the targeted element
 // }
 let testData = [
   {value: "inline", expected: "inline"},
-  {value: "something", expected: "inline"}
+  {value: "inline-block", expected: "inline-block"},
+
+  // Invalid property values should not apply, and should fall back to default
+  {value: "red", expected: "block"},
+  {value: "something", expected: "block"},
+
+  {escape: true, value: "inline", expected: "block"},
+  {escape: true, value: "block", expected: "block"}
 ];
 
 function startTest()
 {
   let style = '#testid {display:block;}';
 
   let styleNode = addStyle(doc, style);
   doc.body.innerHTML = '<div id="testid">Styled Node</div><span>inline element</span>';
@@ -44,28 +51,34 @@ function loopTestData(index)
     return;
   }
 
   let idRuleEditor = ruleView.element.children[1]._ruleEditor;
   let propEditor = idRuleEditor.rule.textProps[0].editor;
   waitForEditorFocus(propEditor.element, function(aEditor) {
     is(inplaceEditor(propEditor.valueSpan), aEditor, "Focused editor should be the value.");
 
+    let thisTest = testData[index];
+
     // Entering a correct value for the property
-    for (let ch of testData[index].value) {
+    for (let ch of thisTest.value) {
       EventUtils.sendChar(ch, ruleWindow);
     }
+    if (thisTest.escape) {
+      EventUtils.synthesizeKey("VK_ESCAPE", {});
+    } else {
+      EventUtils.synthesizeKey("VK_RETURN", {});
+    }
 
     // While the editor is still focused in, the display should have changed already
     executeSoon(() => {
       is(content.getComputedStyle(testElement).display,
         testData[index].expected,
         "Element should be previewed as " + testData[index].expected);
 
-      EventUtils.synthesizeKey("VK_RETURN", {});
       loopTestData(index + 1);
     });
   });
 
   EventUtils.synthesizeMouse(propEditor.valueSpan, 1, 1, {}, ruleWindow);
 }
 
 function finishTest()
--- a/browser/devtools/styleinspector/test/browser_ruleview_ui.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_ui.js
@@ -147,17 +147,17 @@ function testEditProperty()
                 "props[" + i + "] marked dirty as appropriate");
             }
             testDisableProperty();
           }));
         });
 
         for (let ch of "red;") {
           EventUtils.sendChar(ch, ruleWindow);
-          is(propEditor.warning.hidden, ch == "d" || ch == ";",
+          is(propEditor.warning.hidden, true,
             "warning triangle is hidden or shown as appropriate");
         }
       }));
     });
     for (let ch of "border-color:") {
       EventUtils.sendChar(ch, ruleWindow);
     }
   });
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,4 +1,4 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 0.8.478
+Current extension version is: 0.8.505
 
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -15,18 +15,18 @@
  * limitations under the License.
  */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '0.8.478';
-PDFJS.build = '8a4a6f4';
+PDFJS.version = '0.8.505';
+PDFJS.build = 'da1c944';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
@@ -3666,17 +3666,16 @@ var TextAnnotation = (function TextAnnot
       if ((rect[2] - rect[0]) < ANNOT_MIN_SIZE) {
         rect[2] = rect[0] + (rect[3] - rect[1]); // make it square
       }
 
       var container = this.getEmptyContainer('section', rect);
       container.className = 'annotText';
 
       var image = document.createElement('img');
-      image.style.width = container.style.width;
       image.style.height = container.style.height;
       var iconName = item.name;
       image.src = PDFJS.imageResourcesPath + 'annotation-' +
         iconName.toLowerCase() + '.svg';
       image.alt = '[{{type}} Annotation]';
       image.dataset.l10nId = 'text_annotation_type';
       image.dataset.l10nArgs = JSON.stringify({type: iconName});
       var content = document.createElement('div');
@@ -3694,25 +3693,33 @@ var TextAnnotation = (function TextAnnot
         var lines = item.content.split(/(?:\r\n?|\n)/);
         for (var i = 0, ii = lines.length; i < ii; ++i) {
           var line = lines[i];
           e.appendChild(document.createTextNode(line));
           if (i < (ii - 1))
             e.appendChild(document.createElement('br'));
         }
         text.appendChild(e);
-        image.addEventListener('mouseover', function annotationImageOver() {
+
+        var showAnnotation = function showAnnotation() {
           container.style.zIndex += 1;
           content.removeAttribute('hidden');
-        }, false);
-
-        image.addEventListener('mouseout', function annotationImageOut() {
-          container.style.zIndex -= 1;
-          content.setAttribute('hidden', true);
-        }, false);
+        };
+
+        var hideAnnotation = function hideAnnotation(e) {
+          if (e.toElement || e.relatedTarget) { // No context menu is used
+            container.style.zIndex -= 1;
+            content.setAttribute('hidden', true);
+          }
+        };
+
+        content.addEventListener('mouseover', showAnnotation, false);
+        content.addEventListener('mouseout', hideAnnotation, false);
+        image.addEventListener('mouseover', showAnnotation, false);
+        image.addEventListener('mouseout', hideAnnotation, false);
       }
 
       content.appendChild(title);
       content.appendChild(text);
       container.appendChild(image);
       container.appendChild(content);
 
       return container;
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -15,18 +15,18 @@
  * limitations under the License.
  */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '0.8.478';
-PDFJS.build = '8a4a6f4';
+PDFJS.version = '0.8.505';
+PDFJS.build = 'da1c944';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
@@ -2916,17 +2916,16 @@ var TextAnnotation = (function TextAnnot
       if ((rect[2] - rect[0]) < ANNOT_MIN_SIZE) {
         rect[2] = rect[0] + (rect[3] - rect[1]); // make it square
       }
 
       var container = this.getEmptyContainer('section', rect);
       container.className = 'annotText';
 
       var image = document.createElement('img');
-      image.style.width = container.style.width;
       image.style.height = container.style.height;
       var iconName = item.name;
       image.src = PDFJS.imageResourcesPath + 'annotation-' +
         iconName.toLowerCase() + '.svg';
       image.alt = '[{{type}} Annotation]';
       image.dataset.l10nId = 'text_annotation_type';
       image.dataset.l10nArgs = JSON.stringify({type: iconName});
       var content = document.createElement('div');
@@ -2944,25 +2943,33 @@ var TextAnnotation = (function TextAnnot
         var lines = item.content.split(/(?:\r\n?|\n)/);
         for (var i = 0, ii = lines.length; i < ii; ++i) {
           var line = lines[i];
           e.appendChild(document.createTextNode(line));
           if (i < (ii - 1))
             e.appendChild(document.createElement('br'));
         }
         text.appendChild(e);
-        image.addEventListener('mouseover', function annotationImageOver() {
+
+        var showAnnotation = function showAnnotation() {
           container.style.zIndex += 1;
           content.removeAttribute('hidden');
-        }, false);
-
-        image.addEventListener('mouseout', function annotationImageOut() {
-          container.style.zIndex -= 1;
-          content.setAttribute('hidden', true);
-        }, false);
+        };
+
+        var hideAnnotation = function hideAnnotation(e) {
+          if (e.toElement || e.relatedTarget) { // No context menu is used
+            container.style.zIndex -= 1;
+            content.setAttribute('hidden', true);
+          }
+        };
+
+        content.addEventListener('mouseover', showAnnotation, false);
+        content.addEventListener('mouseout', hideAnnotation, false);
+        image.addEventListener('mouseover', showAnnotation, false);
+        image.addEventListener('mouseout', hideAnnotation, false);
       }
 
       content.appendChild(title);
       content.appendChild(text);
       container.appendChild(image);
       container.appendChild(content);
 
       return container;
--- a/browser/extensions/pdfjs/content/web/images/annotation-check.svg
+++ b/browser/extensions/pdfjs/content/web/images/annotation-check.svg
@@ -1,10 +1,11 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <svg
    xmlns="http://www.w3.org/2000/svg"
    width="40"
-   height="40">
+   height="40"
+   viewBox="0 0 40 40">
   <path
      d="M 1.5006714,23.536225 6.8925879,18.994244 14.585721,26.037937 34.019683,4.5410479 38.499329,9.2235032 14.585721,35.458952 z"
      id="path4"
      style="fill:#ffff00;fill-opacity:1;stroke:#000000;stroke-width:1.25402856;stroke-opacity:1" />
 </svg>
--- a/browser/extensions/pdfjs/content/web/images/annotation-comment.svg
+++ b/browser/extensions/pdfjs/content/web/images/annotation-comment.svg
@@ -1,13 +1,14 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <svg
    xmlns="http://www.w3.org/2000/svg"
    height="40"
-   width="40">
+   width="40"
+   viewBox="0 0 40 40">
   <rect
      style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
      width="33.76017"
      height="33.76017"
      x="3.119915"
      y="3.119915" />
   <path
      d="m 20.677967,8.54499 c -7.342801,0 -13.295293,4.954293 -13.295293,11.065751 0,2.088793 0.3647173,3.484376 1.575539,5.150563 L 6.0267418,31.45501 13.560595,29.011117 c 2.221262,1.387962 4.125932,1.665377 7.117372,1.665377 7.3428,0 13.295291,-4.954295 13.295291,-11.065753 0,-6.111458 -5.952491,-11.065751 -13.295291,-11.065751 z"
--- a/browser/extensions/pdfjs/content/web/images/annotation-help.svg
+++ b/browser/extensions/pdfjs/content/web/images/annotation-help.svg
@@ -1,13 +1,14 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <svg
    xmlns="http://www.w3.org/2000/svg"
    width="40"
-   height="40">
+   height="40"
+   viewBox="0 0 40 40">
   <g
      transform="translate(0,-60)"
      id="layer1">
     <rect
        width="36.460953"
        height="34.805603"
        x="1.7695236"
        y="62.597198"
--- a/browser/extensions/pdfjs/content/web/images/annotation-insert.svg
+++ b/browser/extensions/pdfjs/content/web/images/annotation-insert.svg
@@ -1,9 +1,10 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <svg
    xmlns="http://www.w3.org/2000/svg"
    width="64"
-   height="64">
+   height="64"
+   viewBox="0 0 64 64">
   <path
      d="M 32.003143,1.4044602 57.432701,62.632577 6.5672991,62.627924 z"
      style="fill:#ffff00;fill-opacity:0.94117647;fill-rule:nonzero;stroke:#000000;stroke-width:1.00493038;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
 </svg>
--- a/browser/extensions/pdfjs/content/web/images/annotation-key.svg
+++ b/browser/extensions/pdfjs/content/web/images/annotation-key.svg
@@ -1,10 +1,11 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <svg
    xmlns="http://www.w3.org/2000/svg"
    width="64"
-   height="64">
+   height="64"
+   viewBox="0 0 64 64">
   <path
      d="M 25.470843,9.4933766 C 25.30219,12.141818 30.139101,14.445969 34.704831,13.529144 40.62635,12.541995 41.398833,7.3856498 35.97505,5.777863 31.400921,4.1549155 25.157674,6.5445892 25.470843,9.4933766 z M 4.5246282,17.652051 C 4.068249,11.832873 9.2742983,5.9270407 18.437379,3.0977088 29.751911,-0.87185184 45.495663,1.4008022 53.603953,7.1104009 c 9.275765,6.1889221 7.158128,16.2079421 -3.171076,21.5939521 -1.784316,1.635815 -6.380222,1.21421 -7.068351,3.186186 -1.04003,0.972427 -1.288046,2.050158 -1.232864,3.168203 1.015111,2.000108 -3.831548,1.633216 -3.270553,3.759574 0.589477,5.264544 -0.179276,10.53738 -0.362842,15.806257 -0.492006,2.184998 1.163456,4.574232 -0.734888,6.610642 -2.482919,2.325184 -7.30604,2.189143 -9.193497,-0.274767 -2.733688,-1.740626 -8.254447,-3.615254 -6.104247,-6.339626 3.468112,-1.708686 -2.116197,-3.449897 0.431242,-5.080274 5.058402,-1.39256 -2.393215,-2.304318 -0.146889,-4.334645 3.069198,-0.977415 2.056986,-2.518352 -0.219121,-3.540397 1.876567,-1.807151 1.484149,-4.868919 -2.565455,-5.942205 0.150866,-1.805474 2.905737,-4.136876 -1.679967,-5.20493 C 10.260902,27.882167 4.6872697,22.95045 4.5245945,17.652051 z"
      id="path604"
      style="fill:#ffff00;fill-opacity:1;stroke:#000000;stroke-width:1.72665179;stroke-opacity:1" />
 </svg>
--- a/browser/extensions/pdfjs/content/web/images/annotation-newparagraph.svg
+++ b/browser/extensions/pdfjs/content/web/images/annotation-newparagraph.svg
@@ -1,10 +1,11 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <svg
    xmlns="http://www.w3.org/2000/svg"
    width="64"
-   height="64">
+   height="64"
+   viewBox="0 0 64 64">
   <path
      d="M 32.003143,10.913072 57.432701,53.086929 6.567299,53.083723 z"
      id="path2985"
      style="fill:#ffff00;fill-opacity:0.94117647;fill-rule:nonzero;stroke:#000000;stroke-width:0.83403099;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
 </svg>
--- a/browser/extensions/pdfjs/content/web/images/annotation-note.svg
+++ b/browser/extensions/pdfjs/content/web/images/annotation-note.svg
@@ -1,13 +1,14 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <svg
    xmlns="http://www.w3.org/2000/svg"
    width="40"
-   height="40">
+   height="40"
+   viewBox="0 0 40 40">
   <rect
      width="36.075428"
      height="31.096582"
      x="1.962286"
      y="4.4517088"
      id="rect4"
      style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.23004246;stroke-opacity:1" />
   <rect
--- a/browser/extensions/pdfjs/content/web/images/annotation-paragraph.svg
+++ b/browser/extensions/pdfjs/content/web/images/annotation-paragraph.svg
@@ -1,13 +1,14 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <svg
    xmlns="http://www.w3.org/2000/svg"
    width="40"
-   height="40">
+   height="40"
+   viewBox="0 0 40 40">
   <rect
      width="33.76017"
      height="33.76017"
      x="3.119915"
      y="3.119915"
      style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
   <path
      d="m 17.692678,34.50206 0,-16.182224 c -1.930515,-0.103225 -3.455824,-0.730383 -4.57593,-1.881473 -1.12011,-1.151067 -1.680164,-2.619596 -1.680164,-4.405591 0,-1.992435 0.621995,-3.5796849 1.865988,-4.7617553 1.243989,-1.1820288 3.06352,-1.7730536 5.458598,-1.7730764 l 9.802246,0 0,2.6789711 -2.229895,0 0,26.3251486 -2.632515,0 0,-26.3251486 -3.45324,0 0,26.3251486 z"
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..59b3d345f055b098a42a8a7dcabc82328559df29
GIT binary patch
literal 1291
zc$}S7Z)n_P7|->X&b7tCKFqEKv&@3B-sO_^(q7uwOD?(gpx1TRgPj&ybC>seX`8%h
zl6u#IaBid6IYF5s7RG83{j}jBL$vCIUZD<A5FBgi2iZP2HeDH3hwaMwrt4LaQNcHV
z-uHR)`#sO|K7TUR)7??M@6bLB!>S{lVhqjuooDZ3=>7C>B^gaMwv@1YVcO2hCct<F
zCIKGN<UtSvvNAsU4QRtKcUp}n>_oJSO+n2oI~Z?HGcaegwdV{uH4JP#2?kZ2BUb0G
z5V)#v#0h`Y7d1j4qjpZ1pm(A>o|+g=1r(zFdAu#hA_W?-Wjv>i=oXvfh%LP=T07e$
zfp0<VVUBp{R3h4ghoA{?zqiqo@=+AdFkY(3&oER2PWvdD^r4&aP))3#Vd-Xkdl1N)
zsifJM7~ZypB#y|~w!xBQHk<Wk8@<pRB&k3k;OL-f4?=jXaov`49^E=p)*u2aWvYg)
zLLGNxISI#Xjv&fM(2QtwN3m{g2MUFZ%*h5xd3~g&k!7@H$3S{_v=tvW02u=o95YiW
zAL%3iFtt0?0U~eMW2TCdB9DkLHKqaGj))wAzIYW?VMTu!UCk212)sm76wfm)f*=JX
zzaUaV$XC|!C~lL$&^+&#1fFT8D2Z;7T0}8G2`C#aj70NgZbY|iSx<p7SN)qS?#g9D
zCXj7t#vvRjS3pk&+R(~C0}u5w__HTeU4dEaIVU~ao&r;S87N^BYWP-sS@jY2|NGnz
zxyt{E8Ht=B|5YsIEmVQda;Nl>u+uz1M;&9L)`+u(dl=?wjfi|ax14-XDz&@o^HZ-1
z!Gh~-@Y_8_I@s`J@r!3}eOXaFmdF=Bec_Yz!t&Bm%_|#!yz$mk=Np2?jpEcfRaGzJ
z<ZH)&a#L?s_Pa(sUB$)4y<)*t_~qKgz5~HJ2v_$6Z&UkAZ1!&5hg6WnEq?l)cRhXA
zD$m~V;8n>}#ToPZ+RRY@=eFA1b8`akuU@#rCvQ#WvBv52(!%QpKMHqr9Npa9xPSlq
z3+?Zf9{f4KSS+sf4Gau)N|H3Xx&C`LfBUP7djpF5>*<4@7vHX{tv&j<Qo@GT=dWH}
zuRP6<%v^^Y$MFDw`kBL7dD1uEL?%AQ4|~1chd!toVwo!Qi7T_Svp-B$*WNu<<yvT6
z{qB70ft8gNJNSN=vR3$Y`SP7Jb8{ONXL^a7`Jcmi%#9sCck*=Rvmfi7-%~{D7B2}e
Go%{=$J;0^_
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3967cc619606b37c53d2a62478143374fe950949
GIT binary patch
literal 1188
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk?
zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3m8Da#=fE;F*!T6L?J0P
zJu}Z%>HY5gN(z}Nwo2iqz6QPp&Z!xh9#uuD!Bu`C$yM3OmMKd1b_zBXRzL%CQ%e#R
zDspr3imfVamB8j&0ofp7eI*63l9Fs&C5WRUd;=7m^NUgyO!bU)lMM_F70k@^3{6bU
z%nWrDj0_Bo^bLT>OxMuF%GAut$Xo#mlz_GsrKDK}xwt{?0`hE?GD=Dctn~HE%ggo3
zjrH=2()A53EiFN27#ZmTRp=I1=9MH?=;jqG!%T2VElw`VEGWs$&r<;L6O-~wOKg>t
zU|z^AfE$}v3=Jk=fazBx7U&!58R#P^^!3HBG&dKny0|1LH4VS&;*iRMRQ;gT;{4L0
zWMIUlDT6c@SUDG^CYIzEh2-bwz(O$~BfliSI3vG6!8zDWK_fgfFD1XcSQD(?*VoE3
zuec;JFFDoI#a0O@qL-POVrArJVs7Z>YUt!>W^QQcYUF5a?q*@?=;-EV<Y;PX=nT{A
zlAm0fo0?Yw)0=|OYlKrTC@Da6rxs<FrKY$Q<>xAZy=;|<(=9G;xZMKLn}XXd1~~QV
z10AD}7SS*vVCn%e;mH=rfhYXbJYZ@s0w(JWf#^yG21W@_7srr_TS<TZ|F>sWZRl)t
za@fe&<8X%ch>^n@=caw<9sUS(%-_Jk`E0Ir`L5kNcQ*3;a9zTfv%Bo=u1D|Q?PGY-
zI^W^gwpMQOeN3Aw8)nR9c*eMr^(EuWR5qdd#viZSc%|QIYiqB5_4;+U)81n}ti?KU
zdwzJ<)cmPY_-%ON=$QcZM&8K%b$??;6)s%9Ed2lXcm2=j=i6KU`}6bgF&4%*-qUm{
z1;oY8*_TPKzTXg0`SQ}zy9d5(l<Nq0{#Z3Z!h>%WTRlUgh+g!zHR3A@xjC9|>@YEY
zWav`%`r2Afg}&|Ezca<0W0O=c%f7ZIhMSwactQPt0pa$Tn3(_kvQ{N}y1KbW!WTN%
opYUx}*ekN!<rw4nUIs=shPl^k8~E4xfXW~SPgg&ebxsLQ0Kkoy4gdfE
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ffe62936c4a8000c0154cf5616eab93e1739694d
GIT binary patch
literal 3061
zc$@+C3kvj!P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00009a7bBm000XU
z000XU0RWnu7ytkYPiaF#P*7-ZbZ>KLZ*U+<Lqi~Na&Km7Y-Iodc-oy)XH-+^7Crag
z^g>IBfRsybQWXdwQbLP>6p<z>Aqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uh<iVD~V
z<RPMtgQJLw%KPDaqifc@_vX$1wbwr9tn;0-&j-K=43<bUQ8j=JsX`tR;Dg7+#^K~H
zK!FM*Z~zbpvt%K2{UZSY_<lS*D<Z%Lz5oGu(+dayz)hRLFdT>f59&ghTmgWD0l;*T
zI7<kC6aYYajzXpYKt=(8otP$50H6c_V9R4-;{Z@C0AMG7=F<Rxo%or10RUT+Ar%3j
zkpLhQWr#!oXgdI`&sK^>09Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p
z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-<?i
z0%4j!F2Z@488U%158(66005wo6%pWr^Zj_v4zAA5HjcIqUoGmt2LB>rV&neh&#Q1i
z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_<lS*MWK+n+1cgf
z<k(8YLR(?VSAG6x!e78w{cQPuJpA|d;J)G{fihizM+Erb!p!tcr5w+a34~(Y=8s4G
zw+sLL9n&JjNn*KJDiq^U5^;`1nvC-@r6P$!k}1U{(*I=Q-z@tBKHoI}uxdU5dyy@u
zU1J0GOD7Ombim^G008p4Z^6_k2m^p<gW=D2|L;HjN1!DDfM!XOaR2~bL?kX$%CkSm
z2mk;?pn)o|K^yeJ7%adB9Ki+L!3+FgHiSYX#KJ-lLJDMn9CBbOtb#%)hRv`YDqt_v
zKpix|QD}yfa1JiQRk#j4a1Z)n2%f<xynzV>LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW
zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_Ifq<Ex{*7`05XF7hP+2Hl!3BQJ=6@fL%FCo
z8iYoo3(#bAF`ADSpqtQgv>H8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X
zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ<AYmRsNLWl*PS{AOARHt#5!wki2?K;t
z!Y3k=s7tgax)J%r7-BLphge7~Bi0g+6E6^Zh(p9TBoc{3GAFr^0!gu?RMHaCM$&Fl
zBk3%un>0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4
z<uv66WtcKSRim0x-Ke2d5jBrmLam{;Qm;{ms1r1GnmNsb7D-E`t)i9F8fX`2_i3-_
zbh;7Ul^#x)&{xvS=|||7=mYe33=M`AgU5(xC>fg=2N-7=cNnjjOr{yriy6mMFgG#l
znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U
zt5vF<Q0r40Q)j6=sE4X&sBct1q<&fbi3VB2Ov6t@q*0);U*o*SAPZv|vv@2aYYnT0
zb%8a+Cb7-ge0D0knEf5Qi#@8Tp*ce{N;6lpQuCB%KL_KOarm5cP6_8Ir<e17iry6O
zDdH&`rZh~sF=bq9s+O0QSgS~@QL9Jmy*94xr=6y~MY~!1fet~(N+(<=M`w@D1)b+p
z*;C!83a1uLJv#NSE~;y#8=<>IcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya?
z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y
zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB
zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt
z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a<fJbF^|4I#xQ~n$Dc=
zKYhjYmgz5NSkDm8*fZm{6U!;YX`NG>(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C
z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB
zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe
zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0
z?2xS?_ve_-k<Mujg;0Lz*3buG=3$G&ehepthlN*$KaOySSQ^nWmo<0M+(UEUMEXRQ
zMBbZcF;6+KElM>iKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$
z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4
z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu
zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu
z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E
ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw
zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX
z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i&
z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01
z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R
z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw
zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD
zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3|
zawq-H%e&ckC+@AhPrP6BK<z=<L*0kfKU@CX*zeqbYQT4(^U>T#_XdT7&;F71j}Joy
zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z
zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot<a{81DF0~rvGr5Xr~8u`lav1h
z1DNytV>2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F}
z0003XNkl<Zc-o}X%PNIo6vpx2M(pHFC}lu0Fc%Z%BwPenAQKrGaS5168A{4n3>@x2
zi2)(y7!uJoSzBLUJF%_F`wpJ<?^&lzE|>G4D2sOvt2jmz2GEB#<Zy`t?BFH|$b^G6
zTw@zU0XfHW=&I3<do1EG4Ol@ImAJrWh;BpI7>>qKkA*y>BoD$T-tdm2M6&pAVIFsw
z442=4ENW1J9?YP%K+le`fjSH(OXx&tGz`jdiq`^=M$F;^Q~4>J{9$spctS_AgrcJu
zKm)39l?JS1<QIVc*onO~-~prP{vo*wV|c`F8n7JprZ61cPIi76)1g_+uXHALO#PTZ
zEza-~qpb?t4_J!3PzV6cu_V1{iT$|3{!hnLbbJi}?q*PZlw*0P00000NkvXXu0mjf
D7gnmq
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1fe2a8ffefc523d7366d80c5869ae440f5ac7600
GIT binary patch
literal 3066
zc$@+H3kCFvP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00009a7bBm000XU
z000XU0RWnu7ytkYPiaF#P*7-ZbZ>KLZ*U+<Lqi~Na&Km7Y-Iodc-oy)XH-+^7Crag
z^g>IBfRsybQWXdwQbLP>6p<z>Aqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uh<iVD~V
z<RPMtgQJLw%KPDaqifc@_vX$1wbwr9tn;0-&j-K=43<bUQ8j=JsX`tR;Dg7+#^K~H
zK!FM*Z~zbpvt%K2{UZSY_<lS*D<Z%Lz5oGu(+dayz)hRLFdT>f59&ghTmgWD0l;*T
zI7<kC6aYYajzXpYKt=(8otP$50H6c_V9R4-;{Z@C0AMG7=F<Rxo%or10RUT+Ar%3j
zkpLhQWr#!oXgdI`&sK^>09Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p
z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-<?i
z0%4j!F2Z@488U%158(66005wo6%pWr^Zj_v4zAA5HjcIqUoGmt2LB>rV&neh&#Q1i
z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_<lS*MWK+n+1cgf
z<k(8YLR(?VSAG6x!e78w{cQPuJpA|d;J)G{fihizM+Erb!p!tcr5w+a34~(Y=8s4G
zw+sLL9n&JjNn*KJDiq^U5^;`1nvC-@r6P$!k}1U{(*I=Q-z@tBKHoI}uxdU5dyy@u
zU1J0GOD7Ombim^G008p4Z^6_k2m^p<gW=D2|L;HjN1!DDfM!XOaR2~bL?kX$%CkSm
z2mk;?pn)o|K^yeJ7%adB9Ki+L!3+FgHiSYX#KJ-lLJDMn9CBbOtb#%)hRv`YDqt_v
zKpix|QD}yfa1JiQRk#j4a1Z)n2%f<xynzV>LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW
zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_Ifq<Ex{*7`05XF7hP+2Hl!3BQJ=6@fL%FCo
z8iYoo3(#bAF`ADSpqtQgv>H8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X
zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ<AYmRsNLWl*PS{AOARHt#5!wki2?K;t
z!Y3k=s7tgax)J%r7-BLphge7~Bi0g+6E6^Zh(p9TBoc{3GAFr^0!gu?RMHaCM$&Fl
zBk3%un>0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4
z<uv66WtcKSRim0x-Ke2d5jBrmLam{;Qm;{ms1r1GnmNsb7D-E`t)i9F8fX`2_i3-_
zbh;7Ul^#x)&{xvS=|||7=mYe33=M`AgU5(xC>fg=2N-7=cNnjjOr{yriy6mMFgG#l
znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U
zt5vF<Q0r40Q)j6=sE4X&sBct1q<&fbi3VB2Ov6t@q*0);U*o*SAPZv|vv@2aYYnT0
zb%8a+Cb7-ge0D0knEf5Qi#@8Tp*ce{N;6lpQuCB%KL_KOarm5cP6_8Ir<e17iry6O
zDdH&`rZh~sF=bq9s+O0QSgS~@QL9Jmy*94xr=6y~MY~!1fet~(N+(<=M`w@D1)b+p
z*;C!83a1uLJv#NSE~;y#8=<>IcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya?
z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y
zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB
zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt
z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a<fJbF^|4I#xQ~n$Dc=
zKYhjYmgz5NSkDm8*fZm{6U!;YX`NG>(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C
z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB
zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe
zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0
z?2xS?_ve_-k<Mujg;0Lz*3buG=3$G&ehepthlN*$KaOySSQ^nWmo<0M+(UEUMEXRQ
zMBbZcF;6+KElM>iKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$
z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4
z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu
zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu
z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E
ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw
zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX
z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i&
z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01
z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R
z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw
zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD
zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3|
zawq-H%e&ckC+@AhPrP6BK<z=<L*0kfKU@CX*zeqbYQT4(^U>T#_XdT7&;F71j}Joy
zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z
zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot<a{81DF0~rvGr5Xr~8u`lav1h
z1DNytV>2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F}
z0003cNkl<Zc-o}X%PNIo6b9hu*Di{*opLB<GH?O5(ar@pjFq8%2TbGwl-z*af=rk*
zK;}v*1Cq#TM^3fdCTr{O@0YUQ(bu=W^{)3@>s?8`Ue^o?Id{D%ql6(OxW+LKaF0<e
zU^U4om_P+xxWr4`cVIY@_b`Qi%oj4r%Xq?8RD6h7PsC@ih&OydOG<VHk66QQDp{P*
z@P$v*;%h$yWfZZ4^PIj6U<Ko7MJL+QO`OD4$j(C*8=*i{ucZkFagmXo#yd{)0xg<E
zgWx7c)%{<BLySZ({=zi2vjhjxsJU?2Zy3WYp3w(sCzh}jUf9VoQYi}bU_DgJTDcTX
zD~6R{V`S~|#VR&&)JRBjwz08HlQG^JZgGNr+@&8U&93wJ0E#$Gv!F{p{Qv*}07*qo
IM6N<$g5|-pU;qFB
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ddd8a8710be0004a0b5354278b995bc71da06542
GIT binary patch
literal 302
zc$@()0nz@6P)<h;3K|Lk000e1NJLTq000mG000mO1ONa4wfZ;e0002>Nkl<ZcmZRe
z0$^njV^CwzVNhh?Mq$GN*SW_3cR`TVf<X+4&5ba@zJ102t3Y^@!=Awt#^$tQ;6W&{
zD4+lTJP=+HaA0sQTL57T+8~s$Fc@Xb_<sTj&q_Np*umH`_6)p?P$kR^x?veBHGwQP
z%j^>jVQCPyT{?>`SpF~r!)yL042nSJkH5?eFJSCH%nX)52(&_~HTVDa{~P~r<gH@R
zg0ssR1R;);=_~%f;s2`tt2indv|(({G6sH#wEVR4|11A5{=b-|l0g^FE@j|@2GpSp
zpupV!bD2sR#F5y%h)^(R$Y97}NMYbcVN(qN0FGsWNDIB@Hvj+t07*qoM6N<$g0G;2
ASpWb4
--- a/browser/extensions/pdfjs/content/web/viewer.css
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -31,17 +31,17 @@ body {
 body,
 input,
 button,
 select {
   font: message-box;
 }
 
 .hidden {
-  display: none;
+  display: none !important;
 }
 [hidden] {
   display: none !important;
 }
 
 #viewerContainer:-moz-full-screen {
   top: 0px;
   border-top: 2px solid transparent;
@@ -59,17 +59,16 @@ select {
   background-color: #404040;
   background-image: url(images/texture.png);
   width: 100%;
   height: 100%;
   overflow: hidden;
   cursor: none;
 }
 
-
 :-moz-full-screen .page {
   margin-bottom: 100%;
 }
 
 :fullscreen .page {
   margin-bottom: 100%;
 }
 
@@ -209,17 +208,17 @@ html[dir='rtl'] #sidebarContent {
                     linear-gradient(hsla(0,0%,30%,.99), hsla(0,0%,25%,.95));
   box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25),
 
               inset 0 -1px 0 hsla(0,0%,100%,.05),
               0 1px 0 hsla(0,0%,0%,.15),
               0 0 1px hsla(0,0%,0%,.1);
 }
 
-#toolbarContainer, .findbar {
+#toolbarContainer, .findbar, .secondaryToolbar {
   position: relative;
   height: 32px;
   background-color: #474747; /* fallback */
   background-image: url(images/texture.png),
                     linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
   box-shadow: inset 1px 0 0 hsla(0,0%,100%,.08),
               inset 0 1px 1px hsla(0,0%,0%,.15),
               inset 0 -1px 0 hsla(0,0%,100%,.05),
@@ -270,17 +269,17 @@ html[dir='rtl'] #sidebarContent {
 
   background-image: linear-gradient(to right, #999 0%, #fff 50%, #999 100%);
   background-size: 100% 100%;
   background-repeat: no-repeat;
 
   animation: progressIndeterminate 2s linear infinite;
 }
 
-.findbar {
+.findbar, .secondaryToolbar {
   top: 32px;
   position: absolute;
   z-index: 10000;
   height: 32px;
 
   min-width: 16px;
   padding: 0px 6px 0px 6px;
   margin: 4px 2px 4px 2px;
@@ -304,55 +303,81 @@ html[dir='rtl'] .findbar {
 }
 
 #findInput[data-status="pending"] {
   background-image: url(images/loading-small.png);
   background-repeat: no-repeat;
   background-position: right;
 }
 
-.doorHanger {
+.secondaryToolbar {
+  padding: 6px;
+  height: auto;
+  z-index: 30000;
+}
+html[dir='ltr'] .secondaryToolbar {
+  right: 4px;
+}
+html[dir='rtl'] .secondaryToolbar {
+  left: 4px;
+}
+
+#secondaryToolbarButtonContainer {
+  max-width: 200px;
+  max-height: 400px;
+  overflow-y: auto;
+}
+
+.doorHanger,
+.doorHangerRight {
   border: 1px solid hsla(0,0%,0%,.5);
   border-radius: 2px;
   box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
 }
-.doorHanger:after, .doorHanger:before {
+.doorHanger:after, .doorHanger:before,
+.doorHangerRight:after, .doorHangerRight:before {
   bottom: 100%;
   border: solid transparent;
   content: " ";
   height: 0;
   width: 0;
   position: absolute;
   pointer-events: none;
 }
-.doorHanger:after {
+.doorHanger:after,
+.doorHangerRight:after {
   border-bottom-color: hsla(0,0%,32%,.99);
   border-width: 8px;
 }
-.doorHanger:before {
+.doorHanger:before,
+.doorHangerRight:before {
   border-bottom-color: hsla(0,0%,0%,.5);
   border-width: 9px;
 }
 
-html[dir='ltr'] .doorHanger:after {
+html[dir='ltr'] .doorHanger:after,
+html[dir='rtl'] .doorHangerRight:after {
   left: 13px;
   margin-left: -8px;
 }
 
-html[dir='ltr'] .doorHanger:before {
+html[dir='ltr'] .doorHanger:before,
+html[dir='rtl'] .doorHangerRight:before {
   left: 13px;
   margin-left: -9px;
 }
 
-html[dir='rtl'] .doorHanger:after {
+html[dir='rtl'] .doorHanger:after,
+html[dir='ltr'] .doorHangerRight:after {
   right: 13px;
   margin-right: -8px;
 }
 
-html[dir='rtl'] .doorHanger:before {
+html[dir='rtl'] .doorHanger:before,
+html[dir='ltr'] .doorHangerRight:before {
   right: 13px;
   margin-right: -9px;
 }
 
 #findMsg {
   font-style: italic;
   color: #A6B7D0;
 }
@@ -408,31 +433,33 @@ html[dir='ltr'] .splitToolbarButton > .t
   border-radius: 0;
   float: left;
 }
 html[dir='rtl'] .splitToolbarButton > .toolbarButton {
   border-radius: 0;
   float: right;
 }
 
-.toolbarButton {
+.toolbarButton,
+.secondaryToolbarButton {
   border: 0 none;
   background-color: rgba(0, 0, 0, 0);
   width: 32px;
   height: 25px;
 }
 
 .toolbarButton > span {
   display: inline-block;
   width: 0;
   height: 0;
   overflow: hidden;
 }
 
-.toolbarButton[disabled] {
+.toolbarButton[disabled],
+.secondaryToolbarButton[disabled] {
   opacity: .5;
 }
 
 .toolbarButton.group {
   margin-right: 0;
 }
 
 .splitToolbarButton.toggled .toolbarButton {
@@ -483,17 +510,17 @@ html[dir='rtl'] .splitToolbarButton > .t
   margin-left: -1px;
   border-top-right-radius: 2px;
   border-bottom-right-radius: 2px;
   border-left-color: transparent;
 }
 .splitToolbarButtonSeparator {
   padding: 8px 0;
   width: 1px;
-  background-color: hsla(0,0%,00%,.5);
+  background-color: hsla(0,0%,0%,.5);
   z-index: 99;
   box-shadow: 0 0 0 1px hsla(0,0%,100%,.08);
   display: inline-block;
   margin: 5px 0;
 }
 html[dir='ltr'] .splitToolbarButtonSeparator {
   float: left;
 }
@@ -506,22 +533,23 @@ html[dir='rtl'] .splitToolbarButtonSepar
   margin: 1px 0;
   box-shadow: 0 0 0 1px hsla(0,0%,100%,.03);
   transition-property: padding;
   transition-duration: 10ms;
   transition-timing-function: ease;
 }
 
 .toolbarButton,
-.dropdownToolbarButton {
+.dropdownToolbarButton,
+.secondaryToolbarButton {
   min-width: 16px;
   padding: 2px 6px 0;
   border: 1px solid transparent;
   border-radius: 2px;
-  color: hsl(0,0%,95%);
+  color: hsla(0,0%,100%,.8);
   font-size: 12px;
   line-height: 14px;
   -moz-user-select: none;
   /* Opera does not support user-select, use <... unselectable="on"> instead */
   cursor: default;
   transition-property: background-color, border-color, box-shadow;
   transition-duration: 150ms;
   transition-timing-function: ease;
@@ -533,55 +561,60 @@ html[dir='ltr'] .dropdownToolbarButton {
 }
 html[dir='rtl'] .toolbarButton,
 html[dir='rtl'] .dropdownToolbarButton {
   margin: 3px 0 4px 2px;
 }
 
 .toolbarButton:hover,
 .toolbarButton:focus,
-.dropdownToolbarButton {
+.dropdownToolbarButton,
+.secondaryToolbarButton:hover,
+.secondaryToolbarButton:focus {
   background-color: hsla(0,0%,0%,.12);
   background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   background-clip: padding-box;
   border: 1px solid hsla(0,0%,0%,.35);
   border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42);
   box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
               0 0 1px hsla(0,0%,100%,.15) inset,
               0 1px 0 hsla(0,0%,100%,.05);
 }
 
 .toolbarButton:hover:active,
-.dropdownToolbarButton:hover:active {
+.dropdownToolbarButton:hover:active,
+.secondaryToolbarButton:hover:active {
   background-color: hsla(0,0%,0%,.2);
   background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   border-color: hsla(0,0%,0%,.35) hsla(0,0%,0%,.4) hsla(0,0%,0%,.45);
   box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset,
               0 0 1px hsla(0,0%,0%,.2) inset,
               0 1px 0 hsla(0,0%,100%,.05);
   transition-property: background-color, border-color, box-shadow;
   transition-duration: 10ms;
   transition-timing-function: linear;
 }
 
 .toolbarButton.toggled,
-.splitToolbarButton.toggled > .toolbarButton.toggled {
+.splitToolbarButton.toggled > .toolbarButton.toggled,
+.secondaryToolbarButton.toggled {
   background-color: hsla(0,0%,0%,.3);
   background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.45) hsla(0,0%,0%,.5);
   box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset,
               0 0 1px hsla(0,0%,0%,.2) inset,
               0 1px 0 hsla(0,0%,100%,.05);
   transition-property: background-color, border-color, box-shadow;
   transition-duration: 10ms;
   transition-timing-function: linear;
 }
 
 .toolbarButton.toggled:hover:active,
-.splitToolbarButton.toggled > .toolbarButton.toggled:hover:active {
+.splitToolbarButton.toggled > .toolbarButton.toggled:hover:active,
+.secondaryToolbarButton.toggled:hover:active {
   background-color: hsla(0,0%,0%,.4);
   border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.5) hsla(0,0%,0%,.55);
   box-shadow: 0 1px 1px hsla(0,0%,0%,.2) inset,
               0 0 1px hsla(0,0%,0%,.3) inset,
               0 1px 0 hsla(0,0%,100%,.05);
 }
 
 .dropdownToolbarButton {
@@ -640,111 +673,119 @@ html[dir='rtl'] .toolbarButton:first-chi
   height: 1px;
 }
 
 .toolbarButtonFlexibleSpacer {
   -moz-box-flex: 1;
   min-width: 30px;
 }
 
-.toolbarButton#sidebarToggle::before {
-  display: inline-block;
-  content: url(images/toolbarButton-sidebarToggle.png);
-}
-
 html[dir='ltr'] #findPrevious {
   margin-left: 3px;
 }
 html[dir='ltr'] #findNext {
   margin-right: 3px;
 }
 
 html[dir='rtl'] #findPrevious {
   margin-right: 3px;
 }
 html[dir='rtl'] #findNext {
   margin-left: 3px;
 }
-.toolbarButton::before {
+
+.toolbarButton::before,
+.secondaryToolbarButton::before {
   /* All matching images have a size of 16x16
    * (except for the print button: 18x16)
    * All relevant containers have a size of 32x25 */
   position: absolute;
+  display: inline-block;
   top: 4px;
   left: 7px;
 }
+html[dir="ltr"] .secondaryToolbarButton::before {
+  left: 4px;
+}
+html[dir="rtl"] .secondaryToolbarButton::before {
+  right: 4px;
+}
+
+.toolbarButton#sidebarToggle::before {
+  content: url(images/toolbarButton-sidebarToggle.png);
+}
+
+.toolbarButton#secondaryToolbarToggle::before {
+  content: url(images/toolbarButton-secondaryToolbarToggle.png);
+}
 
 html[dir='ltr'] .toolbarButton.findPrevious::before {
-  display: inline-block;
   content: url(images/findbarButton-previous.png);
 }
 
 html[dir='rtl'] .toolbarButton.findPrevious::before {
-  display: inline-block;
   content: url(images/findbarButton-previous-rtl.png);
 }
 
 html[dir='ltr'] .toolbarButton.findNext::before {
-  display: inline-block;
   content: url(images/findbarButton-next.png);
 }
 
 html[dir='rtl'] .toolbarButton.findNext::before {
-  display: inline-block;
   content: url(images/findbarButton-next-rtl.png);
 }
 
 html[dir='ltr'] .toolbarButton.pageUp::before {
-  display: inline-block;
   content: url(images/toolbarButton-pageUp.png);
 }
 
 html[dir='rtl'] .toolbarButton.pageUp::before {
-  display: inline-block;
   content: url(images/toolbarButton-pageUp-rtl.png);
 }
 
 html[dir='ltr'] .toolbarButton.pageDown::before {
-  display: inline-block;
   content: url(images/toolbarButton-pageDown.png);
 }
 
 html[dir='rtl'] .toolbarButton.pageDown::before {
-  display: inline-block;
   content: url(images/toolbarButton-pageDown-rtl.png);
 }
 
 .toolbarButton.zoomOut::before {
-  display: inline-block;
   content: url(images/toolbarButton-zoomOut.png);
 }
 
 .toolbarButton.zoomIn::before {
-  display: inline-block;
   content: url(images/toolbarButton-zoomIn.png);
 }
 
-.toolbarButton.presentationMode::before {
-  display: inline-block;
+.toolbarButton.presentationMode::before,
+.secondaryToolbarButton.presentationMode::before {
   content: url(images/toolbarButton-presentationMode.png);
 }
 
-.toolbarButton.print::before {
-  display: inline-block;
+.toolbarButton.print::before,
+.secondaryToolbarButton.print::before {
   content: url(images/toolbarButton-print.png);
   left: 6px;
 }
+html[dir="ltr"] .secondaryToolbarButton.print::before {
+  left: 3px;
+}
+html[dir="rtl"] .secondaryToolbarButton.print::before {
+  right: 3px;
+}
 
-.toolbarButton.openFile::before {
-  display: inline-block;
+.toolbarButton.openFile::before,
+.secondaryToolbarButton.openFile::before {
   content: url(images/toolbarButton-openFile.png);
 }
 
-.toolbarButton.download::before {
-  display: inline-block;
+.toolbarButton.download::before,
+.secondaryToolbarButton.download::before {
   content: url(images/toolbarButton-download.png);
 }
 
 .toolbarButton.bookmark {
   -moz-box-sizing: border-box;
   box-sizing: border-box;
   margin-top: 3px;
   padding-top: 4px;
@@ -755,30 +796,96 @@ html[dir='rtl'] .toolbarButton.pageDown:
   pointer-events: none;
 }
 
 .toolbarButton.bookmark::before {
   content: url(images/toolbarButton-bookmark.png);
 }
 
 #viewThumbnail.toolbarButton::before {
-  display: inline-block;
   content: url(images/toolbarButton-viewThumbnail.png);
 }
 
 #viewOutline.toolbarButton::before {
-  display: inline-block;
   content: url(images/toolbarButton-viewOutline.png);
 }
 
 #viewFind.toolbarButton::before {
-  display: inline-block;
   content: url(images/toolbarButton-search.png);
 }
 
+.secondaryToolbarButton {
+  position: relative;
+  margin: 0 0 4px 0;
+  padding: 3px 0 1px 0;
+  height: auto;
+  min-height: 25px;
+  width: auto;
+  min-width: 100%;
+  white-space: normal;
+}
+html[dir="ltr"] .secondaryToolbarButton {
+  padding-left: 24px;
+  text-align: left;
+}
+html[dir="rtl"] .secondaryToolbarButton {
+  padding-right: 24px;
+  text-align: right;
+}
+
+#secondaryToolbarButtonContainer :last-child {
+  margin-bottom: 0;
+}
+
+html[dir="ltr"] .secondaryToolbarButton > span {
+  padding-right: 4px;
+}
+html[dir="rtl"] .secondaryToolbarButton > span {
+  padding-left: 4px;
+}
+
+.secondaryToolbarButton.firstPage::before {
+  content: url(images/secondaryToolbarButton-firstPage.png);
+}
+
+.secondaryToolbarButton.lastPage::before {
+  content: url(images/secondaryToolbarButton-lastPage.png);
+}
+
+.secondaryToolbarButton.rotateCcw::before {
+  content: url(images/secondaryToolbarButton-rotateCcw.png);
+}
+
+.secondaryToolbarButton.rotateCw::before {
+  content: url(images/secondaryToolbarButton-rotateCw.png);
+}
+
+.verticalToolbarSeparator {
+  display: block;
+  padding: 8px 0;
+  margin: 8px 4px;
+  width: 1px;
+  background-color: hsla(0,0%,0%,.5);
+  box-shadow: 0 0 0 1px hsla(0,0%,100%,.08);
+}
+html[dir='ltr'] .verticalToolbarSeparator {
+  margin-left: 2px;
+}
+html[dir='rtl'] .verticalToolbarSeparator {
+  margin-right: 2px;
+}
+
+.horizontalToolbarSeparator {
+  display: block; 
+  margin: 0 0 4px 0;
+  height: 1px;
+  width: 100%;
+  background-color: hsla(0,0%,0%,.5);
+  box-shadow: 0 0 0 1px hsla(0,0%,100%,.08);
+}
 
 .toolbarField {
   padding: 3px 6px;
   margin: 4px 0 4px 0;
   border: 1px solid transparent;
   border-radius: 2px;
   background-color: hsla(0,0%,100%,.09);
   background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
@@ -994,22 +1101,24 @@ canvas {
   position: relative;
   overflow: visible;
   border: 9px solid transparent;
   background-clip: content-box;
   border-image: url(images/shadow.png) 9 9 repeat;
   background-color: white;
 }
 
-.page > a {
+.page > a,
+.annotationLayer > a {
   display: block;
   position: absolute;
 }
 
-.page > a:hover {
+.page > a:hover,
+.annotationLayer > a:hover {
   opacity: 0.2;
   background: #ff0;
   box-shadow: 0px 2px 10px #ff0;
 }
 
 .loadingIcon {
   position: absolute;
   display: block;
@@ -1080,17 +1189,16 @@ canvas {
 }
 
 .annotText > img {
   position: absolute;
   opacity: 0.6;
 }
 
 .annotText > img:hover {
-  cursor: pointer;
   opacity: 1;
 }
 
 .annotText > div > h1 {
   font-size: 1.2em;
   border-bottom: 1px solid #000000;
   margin: 0px;
 }
@@ -1204,17 +1312,17 @@ canvas {
 
 @media print {
   /* General rules for printing. */
   body {
     background: transparent none;
   }
 
   /* Rules for browsers that don't support mozPrintCallback. */
-  #sidebarContainer, .toolbar, #loadingBox, #errorWrapper, .textLayer {
+  #sidebarContainer, #secondaryToolbar, .toolbar, #loadingBox, #errorWrapper, .textLayer {
     display: none;
   }
   #viewerContainer {
     overflow: visible;
   }
 
   #mainContainer, #viewerContainer, .page, .page canvas {
     position: static;
@@ -1242,26 +1350,50 @@ canvas {
   }
   #printContainer canvas {
     position: relative;
     top: 0;
     left: 0;
   }
 }
 
-@media all and (max-width: 950px) {
+.visibleLargeView,
+.visibleMediumView,
+.visibleSmallView {
+  display: none;
+}
+
+@media all and (max-width: 960px) {
   html[dir='ltr'] #outerContainer.sidebarMoving .outerCenter,
   html[dir='ltr'] #outerContainer.sidebarOpen .outerCenter {
     float: left;
-    left: 180px;
+    left: 185px;
   }
   html[dir='rtl'] #outerContainer.sidebarMoving .outerCenter,
   html[dir='rtl'] #outerContainer.sidebarOpen .outerCenter {
     float: right;
-    right: 180px;
+    right: 185px;
+  }
+}
+
+@media all and (max-width: 900px) {
+  .sidebarOpen .hiddenLargeView {
+    display: none;
+  }
+  .sidebarOpen .visibleLargeView {
+    display: inherit;
+  }
+}
+
+@media all and (max-width: 860px) {
+  .sidebarOpen .hiddenMediumView {
+    display: none;
+  }
+  .sidebarOpen .visibleMediumView {
+    display: inherit;
   }
 }
 
 @media all and (max-width: 770px) {
   #sidebarContainer {
     top: 32px;
     z-index: 100;
   }
@@ -1277,37 +1409,71 @@ canvas {
     left: 0px;
   }
   html[dir='rtl'] #outerContainer.sidebarOpen > #mainContainer {
     right: 0px;
   }
 
   html[dir='ltr'] .outerCenter {
     float: left;
-    left: 180px;
+    left: 185px;
   }
   html[dir='rtl'] .outerCenter {
     float: right;
-    right: 180px;
+    right: 185px;
+  }
+
+  #outerContainer .hiddenLargeView,
+  #outerContainer .hiddenMediumView {
+    display: inherit;
+  }
+  #outerContainer .visibleLargeView,
+  #outerContainer .visibleMediumView {
+    display: none;
+  }
+}
+
+@media all and (max-width: 700px) {
+  #outerContainer .hiddenLargeView {
+    display: none;
+  }
+  #outerContainer .visibleLargeView {
+    display: inherit;
+  }
+}
+
+@media all and (max-width: 660px) {
+  #outerContainer .hiddenMediumView {
+    display: none;
+  }
+  #outerContainer .visibleMediumView {
+    display: inherit;
   }
 }
 
 @media all and (max-width: 600px) {
   .hiddenSmallView {
     display: none;
   }
+  .visibleSmallView {
+    display: inherit;
+  }
+  html[dir='ltr'] #outerContainer.sidebarMoving .outerCenter,
+  html[dir='ltr'] #outerContainer.sidebarOpen .outerCenter,
   html[dir='ltr'] .outerCenter {
     left: 156px;
   }
-  html[dir='rtr'] .outerCenter {
+  html[dir='rtl'] #outerContainer.sidebarMoving .outerCenter,
+  html[dir='rtl'] #outerContainer.sidebarOpen .outerCenter,
+  html[dir='rtl'] .outerCenter {
     right: 156px;
   }
   .toolbarButtonSpacer {
     width: 0;
   }
 }
 
-@media all and (max-width: 500px) {
+@media all and (max-width: 510px) {
   #scaleSelectContainer, #pageNumberLabel {
     display: none;
   }
 }
 
--- a/browser/extensions/pdfjs/content/web/viewer.html
+++ b/browser/extensions/pdfjs/content/web/viewer.html
@@ -58,32 +58,71 @@ limitations under the License.
           <div id="outlineView" class="hidden">
           </div>
         </div>
       </div>  <!-- sidebarContainer -->
 
       <div id="mainContainer">
         <div class="findbar hidden doorHanger hiddenSmallView" id="findbar">
           <label for="findInput" class="toolbarLabel" data-l10n-id="find_label">Find:</label>
-          <input id="findInput" class="toolbarField" tabindex="21">
+          <input id="findInput" class="toolbarField" tabindex="41">
           <div class="splitToolbarButton">
-            <button class="toolbarButton findPrevious" title="" id="findPrevious" tabindex="22" data-l10n-id="find_previous">
+            <button class="toolbarButton findPrevious" title="" id="findPrevious" tabindex="42" data-l10n-id="find_previous">
               <span data-l10n-id="find_previous_label">Previous</span>
             </button>
             <div class="splitToolbarButtonSeparator"></div>
-            <button class="toolbarButton findNext" title="" id="findNext" tabindex="23" data-l10n-id="find_next">
+            <button class="toolbarButton findNext" title="" id="findNext" tabindex="43" data-l10n-id="find_next">
               <span data-l10n-id="find_next_label">Next</span>
             </button>
           </div>
           <input type="checkbox" id="findHighlightAll" class="toolbarField">
-          <label for="findHighlightAll" class="toolbarLabel" tabindex="24" data-l10n-id="find_highlight">Highlight all</label>
+          <label for="findHighlightAll" class="toolbarLabel" tabindex="44" data-l10n-id="find_highlight">Highlight all</label>
           <input type="checkbox" id="findMatchCase" class="toolbarField">
-          <label for="findMatchCase" class="toolbarLabel" tabindex="25" data-l10n-id="find_match_case_label">Match case</label>
+          <label for="findMatchCase" class="toolbarLabel" tabindex="45" data-l10n-id="find_match_case_label">Match case</label>
           <span id="findMsg" class="toolbarLabel"></span>
-        </div>
+        </div>  <!-- findbar -->
+
+        <div id="secondaryToolbar" class="secondaryToolbar hidden doorHangerRight">
+          <div id="secondaryToolbarButtonContainer">
+            <button id="secondaryPresentationMode" class="secondaryToolbarButton presentationMode visibleLargeView" title="Switch to Presentation Mode" tabindex="18" data-l10n-id="presentation_mode">
+              <span data-l10n-id="presentation_mode_label">Presentation Mode</span>
+            </button>
+
+            <button id="secondaryOpenFile" class="secondaryToolbarButton openFile visibleLargeView" title="Open File" tabindex="19" data-l10n-id="open_file">
+              <span data-l10n-id="open_file_label">Open</span>
+            </button>
+
+            <button id="secondaryPrint" class="secondaryToolbarButton print visibleMediumView" title="Print" tabindex="20" data-l10n-id="print">
+              <span data-l10n-id="print_label">Print</span>
+            </button>
+
+            <button id="secondaryDownload" class="secondaryToolbarButton download visibleMediumView" title="Download" tabindex="21" data-l10n-id="download">
+              <span data-l10n-id="download_label">Download</span>
+            </button>
+
+            <div class="horizontalToolbarSeparator visibleLargeView"></div>
+
+            <button id="firstPage" class="secondaryToolbarButton firstPage" title="Go to First Page" tabindex="22" data-l10n-id="first_page">
+              <span data-l10n-id="first_page_label">Go to First Page</span>
+            </button>
+            <button id="lastPage" class="secondaryToolbarButton lastPage" title="Go to Last Page" tabindex="23" data-l10n-id="last_page">
+              <span data-l10n-id="last_page_label">Go to Last Page</span>
+            </button>
+
+            <div class="horizontalToolbarSeparator"></div>
+
+            <button id="pageRotateCw" class="secondaryToolbarButton rotateCw" title="Rotate Clockwise" tabindex="24" data-l10n-id="page_rotate_cw">
+              <span data-l10n-id="page_rotate_cw_label">Rotate Clockwise</span>
+            </button>
+            <button id="pageRotateCcw" class="secondaryToolbarButton rotateCcw" title="Rotate Counterclockwise" tabindex="25" data-l10n-id="page_rotate_ccw">
+              <span data-l10n-id="page_rotate_ccw_label">Rotate Counterclockwise</span>
+            </button>
+          </div>
+        </div>  <!-- secondaryToolbar -->
+
         <div class="toolbar">
           <div id="toolbarContainer">
             <div id="toolbarViewer">
               <div id="toolbarViewerLeft">
                 <button id="sidebarToggle" class="toolbarButton" title="Toggle Sidebar" tabindex="4" data-l10n-id="toggle_sidebar">
                   <span data-l10n-id="toggle_sidebar_label">Toggle Sidebar</span>
                 </button>
                 <div class="toolbarButtonSpacer"></div>
@@ -100,33 +139,39 @@ limitations under the License.
                   </button>
                 </div>
                 <label id="pageNumberLabel" class="toolbarLabel" for="pageNumber" data-l10n-id="page_label">Page: </label>
                 <input type="number" id="pageNumber" class="toolbarField pageNumber" value="1" size="4" min="1" tabindex="8">
                 </input>
                 <span id="numPages" class="toolbarLabel"></span>
               </div>
               <div id="toolbarViewerRight">
-                <button id="presentationMode" class="toolbarButton presentationMode hiddenSmallView" title="Switch to Presentation Mode" tabindex="12" data-l10n-id="presentation_mode">
+                <button id="presentationMode" class="toolbarButton presentationMode hiddenLargeView" title="Switch to Presentation Mode" tabindex="12" data-l10n-id="presentation_mode">
                   <span data-l10n-id="presentation_mode_label">Presentation Mode</span>
                 </button>
 
-                <button id="openFile" class="toolbarButton openFile hiddenSmallView" title="Open File" tabindex="13" data-l10n-id="open_file">
-                   <span data-l10n-id="open_file_label">Open</span>
+                <button id="openFile" class="toolbarButton openFile hiddenLargeView" title="Open File" tabindex="13" data-l10n-id="open_file">
+                  <span data-l10n-id="open_file_label">Open</span>
                 </button>
 
-                <button id="print" class="toolbarButton print" title="Print" tabindex="14" data-l10n-id="print">
+                <button id="print" class="toolbarButton print hiddenMediumView" title="Print" tabindex="14" data-l10n-id="print">
                   <span data-l10n-id="print_label">Print</span>
                 </button>
 
-                <button id="download" class="toolbarButton download" title="Download" tabindex="15" data-l10n-id="download">
+                <button id="download" class="toolbarButton download hiddenMediumView" title="Download" tabindex="15" data-l10n-id="download">
                   <span data-l10n-id="download_label">Download</span>
                 </button>
                 <!-- <div class="toolbarButtonSpacer"></div> -->
                 <a href="#" id="viewBookmark" class="toolbarButton bookmark hiddenSmallView" title="Current view (copy or open in new window)" tabindex="16" data-l10n-id="bookmark"><span data-l10n-id="bookmark_label">Current View</span></a>
+
+                <div class="verticalToolbarSeparator hiddenSmallView"></div>
+                
+                <button id="secondaryToolbarToggle" class="toolbarButton" title="Tools" tabindex="17" data-l10n-id="tools">
+                  <span data-l10n-id="tools_label">Tools</span>
+                </button> 
               </div>
               <div class="outerCenter">
                 <div class="innerCenter" id="toolbarViewerMiddle">
                   <div class="splitToolbarButton">
                     <button id="zoomOut" class="toolbarButton zoomOut" title="Zoom Out" tabindex="9" data-l10n-id="zoom_out">
                       <span data-l10n-id="zoom_out_label">Zoom Out</span>
                     </button>
                     <div class="splitToolbarButtonSeparator"></div>
@@ -157,28 +202,28 @@ limitations under the License.
                 <div class="glimmer">
                 </div>
               </div>
             </div>
           </div>
         </div>
 
         <menu type="context" id="viewerContextMenu">
-          <menuitem id="firstPage" label="First Page"
-                    data-l10n-id="first_page" ></menuitem>
-          <menuitem id="lastPage" label="Last Page"
-                    data-l10n-id="last_page" ></menuitem>
-          <menuitem id="pageRotateCcw" label="Rotate Counter-Clockwise"
-                    data-l10n-id="page_rotate_ccw" ></menuitem>
-          <menuitem id="pageRotateCw" label="Rotate Clockwise"
-                    data-l10n-id="page_rotate_cw" ></menuitem>
+          <menuitem id="contextFirstPage" label="First Page"
+                    data-l10n-id="first_page"></menuitem>
+          <menuitem id="contextLastPage" label="Last Page"
+                    data-l10n-id="last_page"></menuitem>
+          <menuitem id="contextPageRotateCw" label="Rotate Clockwise"
+                    data-l10n-id="page_rotate_cw"></menuitem>
+          <menuitem id="contextPageRotateCcw" label="Rotate Counter-Clockwise"
+                    data-l10n-id="page_rotate_ccw"></menuitem>
         </menu>
 
       <div id="viewerContainer">   
-          <div id="viewer" contextmenu="viewerContextMenu"></div>
+          <div id="viewer"></div>
         </div>
 
         <div id="errorWrapper" hidden='true'>
           <div id="errorMessageLeft">
             <span id="errorMessage"></span>
             <button id="errorShowMore" data-l10n-id="error_more_info">
               More Information
             </button>
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -12,17 +12,17 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 /* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, PDFFindBar, CustomStyle,
            PDFFindController, ProgressBar, TextLayerBuilder, DownloadManager,
            getFileName, getOutputScale, scrollIntoView, getPDFFileNameFromURL,
-           PDFHistory, ThumbnailView, noContextMenuHandler */
+           PDFHistory, ThumbnailView, noContextMenuHandler, SecondaryToolbar */
 
 'use strict';
 
 var DEFAULT_URL = 'compressed.tracemonkey-pldi-09.pdf';
 var DEFAULT_SCALE = 'auto';
 var DEFAULT_SCALE_DELTA = 1.1;
 var UNKNOWN_SCALE = 0;
 var CACHE_SIZE = 20;
@@ -425,18 +425,16 @@ var Settings = (function SettingsClosure
   };
 
   return Settings;
 })();
 
 var cache = new Cache(CACHE_SIZE);
 var currentPageNumber = 1;
 
-// TODO: Enable the FindBar *AFTER* the pagesPromise in the load function
-// got resolved
 
 /* globals PDFFindController, FindStates, mozL10n */
 
 /**
  * Creates a "search bar" given set of DOM elements
  * that act as controls for searching, or for setting
  * search preferences in the UI. This object also sets
  * up the appropriate events for the controls. Actual
@@ -634,16 +632,18 @@ var PDFFindController = {
   dirtyMatch: false,
 
   findTimeout: null,
 
   pdfPageSource: null,
 
   integratedFind: false,
 
+  firstPagePromise: new PDFJS.Promise(),
+
   initialize: function(options) {
     if(typeof PDFFindBar === 'undefined' || PDFFindBar === null) {
         throw 'PDFFindController cannot be initialized ' +
             'without a PDFFindController instance';
     }
 
     this.pdfPageSource = options.pdfPageSource;
     this.integratedFind = options.integratedFind;
@@ -742,25 +742,27 @@ var PDFFindController = {
 
   handleEvent: function(e) {
     if (this.state === null || e.type !== 'findagain') {
       this.dirtyMatch = true;
     }
     this.state = e.detail;
     this.updateUIState(FindStates.FIND_PENDING);
 
-    this.extractText();
-
-    clearTimeout(this.findTimeout);
-    if (e.type === 'find') {
-      // Only trigger the find action after 250ms of silence.
-      this.findTimeout = setTimeout(this.nextMatch.bind(this), 250);
-    } else {
-      this.nextMatch();
-    }
+    this.firstPagePromise.then(function() {
+      this.extractText();
+
+      clearTimeout(this.findTimeout);
+      if (e.type === 'find') {
+        // Only trigger the find action after 250ms of silence.
+        this.findTimeout = setTimeout(this.nextMatch.bind(this), 250);
+      } else {
+        this.nextMatch();
+      }
+    }.bind(this));
   },
 
   updatePage: function(idx) {
     var page = this.pdfPageSource.pages[idx];
 
     if (this.selected.pageIdx === idx) {
       // If the page is selected, scroll the page into view, which triggers
       // rendering the page, which adds the textLayer. Once the textLayer is
@@ -1136,16 +1138,32 @@ var PDFHistory = {
       this._pushToHistory(params, true);
     }
   },
 
   _getPreviousParams: function pdfHistory_getPreviousParams(onlyCheckPage,
                                                             beforeUnload) {
     if (!(this.currentBookmark && this.currentPage)) {
       return null;
+    } else if (this.updatePreviousBookmark) {
+      this.updatePreviousBookmark = false;
+    }
+    if (this.uid > 0 && !(this.previousBookmark && this.previousPage)) {
+      // Prevent the history from getting stuck in the current state,
+      // effectively preventing the user from going back/forward in the history.
+      //
+      // This happens if the current position in the document didn't change when
+      // the history was previously updated. The reasons for this are either:
+      // 1. The current zoom value is such that the document does not need to,
+      //    or cannot, be scrolled to display the destination.
+      // 2. The previous destination is broken, and doesn't actally point to a
+      //    position within the document.
+      //    (This is either due to a bad PDF generator, or the user making a
+      //     mistake when entering a destination in the hash parameters.)
+      return null;
     }
     if ((!this.current.dest && !onlyCheckPage) || beforeUnload) {
       if (this.previousBookmark === this.currentBookmark) {
         return null;
       }
     } else if (this.current.page || onlyCheckPage) {
       if (this.previousPage === this.currentPage) {
         return null;
@@ -1170,17 +1188,18 @@ var PDFHistory = {
       return;
     }
     if (!params.hash && params.page) {
       params.hash = ('page=' + params.page);
     }
     if (addPrevious && !overwrite) {
       var previousParams = this._getPreviousParams();
       if (previousParams) {
-        var replacePrevious = (this.current.hash !== this.previousHash);
+        var replacePrevious = (!this.current.dest &&
+                               this.current.hash !== this.previousHash);
         this._pushToHistory(previousParams, false, replacePrevious);
       }
     }
     if (overwrite || this.uid === 0) {
       window.history.replaceState(this._stateObj(params), '');
     } else {
       window.history.pushState(this._stateObj(params), '');
     }
@@ -1243,16 +1262,137 @@ var PDFHistory = {
       } else if (direction === 1 && state && state.uid < (this.uid - 1)) {
         window.history.forward();
       }
     }
   }
 };
 
 
+var SecondaryToolbar = {
+  opened: false,
+  previousContainerHeight: null,
+  newContainerHeight: null,
+
+  initialize: function secondaryToolbarInitialize(options) {
+    this.toolbar = options.toolbar;
+    this.toggleButton = options.toggleButton;
+
+    this.buttonContainer = this.toolbar.firstElementChild;
+
+    // Define the toolbar buttons.
+    this.presentationMode = options.presentationMode;
+    this.openFile = options.openFile;
+    this.print = options.print;
+    this.download = options.download;
+    this.firstPage = options.firstPage;
+    this.lastPage = options.lastPage;
+    this.pageRotateCw = options.pageRotateCw;
+    this.pageRotateCcw = options.pageRotateCcw;
+
+    // Attach the event listeners.
+    this.toggleButton.addEventListener('click', this.toggle.bind(this));
+
+    this.presentationMode.addEventListener('click',
+      this.presentationModeClick.bind(this));
+    this.openFile.addEventListener('click', this.openFileClick.bind(this));
+    this.print.addEventListener('click', this.printClick.bind(this));
+    this.download.addEventListener('click', this.downloadClick.bind(this));
+
+    this.firstPage.addEventListener('click', this.firstPageClick.bind(this));
+    this.lastPage.addEventListener('click', this.lastPageClick.bind(this));
+
+    this.pageRotateCw.addEventListener('click',
+      this.pageRotateCwClick.bind(this));
+    this.pageRotateCcw.addEventListener('click',
+      this.pageRotateCcwClick.bind(this));
+  },
+
+  // Event handling functions.
+  presentationModeClick: function secondaryToolbarPresentationModeClick(evt) {
+    PDFView.presentationMode();
+    this.close();
+  },
+
+  openFileClick: function secondaryToolbarOpenFileClick(evt) {
+    document.getElementById('fileInput').click();
+    this.close(evt.target);
+  },
+
+  printClick: function secondaryToolbarPrintClick(evt) {
+    window.print();
+    this.close(evt.target);
+  },
+
+  downloadClick: function secondaryToolbarDownloadClick(evt) {
+    PDFView.download();
+    this.close(evt.target);
+  },
+
+  firstPageClick: function secondaryToolbarFirstPageClick(evt) {
+    PDFView.page = 1;
+  },
+
+  lastPageClick: function secondaryToolbarLastPageClick(evt) {
+    PDFView.page = PDFView.pdfDocument.numPages;
+  },
+
+  pageRotateCwClick: function secondaryToolbarPageRotateCwClick(evt) {
+    PDFView.rotatePages(90);
+  },
+
+  pageRotateCcwClick: function secondaryToolbarPageRotateCcwClick(evt) {
+    PDFView.rotatePages(-90);
+  },
+
+  // Misc. functions for interacting with the toolbar.
+  setMaxHeight: function secondaryToolbarSetMaxHeight(container) {
+    this.newContainerHeight = container.clientHeight;
+    if (this.previousContainerHeight === this.newContainerHeight) {
+      return;
+    }
+    this.buttonContainer.setAttribute('style',
+      'max-height: ' + (this.newContainerHeight - SCROLLBAR_PADDING) + 'px;');
+    this.previousContainerHeight = this.newContainerHeight;
+  },
+
+  open: function secondaryToolbarOpen() {
+    if (this.opened) {
+      return;
+    }
+    this.opened = true;
+    this.toggleButton.classList.add('toggled');
+    this.toolbar.classList.remove('hidden');
+  },
+
+  close: function secondaryToolbarClose(target) {
+    if (!this.opened) {
+      return;
+    } else if (target && !this.toolbar.contains(target)) {
+      return;
+    }
+    this.opened = false;
+    this.toolbar.classList.add('hidden');
+    this.toggleButton.classList.remove('toggled');
+  },
+
+  toggle: function secondaryToolbarToggle() {
+    if (this.opened) {
+      this.close();
+    } else {
+      this.open();
+    }
+  },
+
+  get isOpen() {
+    return this.opened;
+  }
+};
+
+
 var PDFView = {
   pages: [],
   thumbnails: [],
   currentScale: UNKNOWN_SCALE,
   currentScaleValue: null,
   initialBookmark: document.location.hash.substring(1),
   container: null,
   thumbnailContainer: null,
@@ -1279,31 +1419,44 @@ var PDFView = {
     this.watchScroll(container, this.pageViewScroll, updateViewarea);
 
     var thumbnailContainer = this.thumbnailContainer =
                              document.getElementById('thumbnailView');
     this.thumbnailViewScroll = {};
     this.watchScroll(thumbnailContainer, this.thumbnailViewScroll,
                      this.renderHighestPriority.bind(this));
 
+    SecondaryToolbar.initialize({
+      toolbar: document.getElementById('secondaryToolbar'),
+      toggleButton: document.getElementById('secondaryToolbarToggle'),
+      presentationMode: document.getElementById('secondaryPresentationMode'),
+      openFile: document.getElementById('secondaryOpenFile'),
+      print: document.getElementById('secondaryPrint'),
+      download: document.getElementById('secondaryDownload'),
+      firstPage: document.getElementById('firstPage'),
+      lastPage: document.getElementById('lastPage'),
+      pageRotateCw: document.getElementById('pageRotateCw'),
+      pageRotateCcw: document.getElementById('pageRotateCcw')
+    });
+
     PDFFindBar.initialize({
-        bar: document.getElementById('findbar'),
-        toggleButton: document.getElementById('viewFind'),
-        findField: document.getElementById('findInput'),
-        highlightAllCheckbox: document.getElementById('findHighlightAll'),
-        caseSensitiveCheckbox: document.getElementById('findMatchCase'),
-        findMsg: document.getElementById('findMsg'),
-        findStatusIcon: document.getElementById('findStatusIcon'),
-        findPreviousButton: document.getElementById('findPrevious'),
-        findNextButton: document.getElementById('findNext')
+      bar: document.getElementById('findbar'),
+      toggleButton: document.getElementById('viewFind'),
+      findField: document.getElementById('findInput'),
+      highlightAllCheckbox: document.getElementById('findHighlightAll'),
+      caseSensitiveCheckbox: document.getElementById('findMatchCase'),
+      findMsg: document.getElementById('findMsg'),
+      findStatusIcon: document.getElementById('findStatusIcon'),
+      findPreviousButton: document.getElementById('findPrevious'),
+      findNextButton: document.getElementById('findNext')
     });
 
     PDFFindController.initialize({
-        pdfPageSource: this,
-        integratedFind: this.supportsIntegratedFind
+      pdfPageSource: this,
+      integratedFind: this.supportsIntegratedFind
     });
 
     this.initialized = true;
     container.addEventListener('scroll', function() {
       self.lastScroll = Date.now();
     }, false);
   },
 
@@ -1386,27 +1539,33 @@ var PDFView = {
         scale = Math.min(1.0, pageWidthScale);
         break;
     }
     this.setScale(scale, resetAutoSettings, noScroll);
 
     selectScaleOption(value);
   },
 
-  zoomIn: function pdfViewZoomIn() {
-    var newScale = (this.currentScale * DEFAULT_SCALE_DELTA).toFixed(2);
-    newScale = Math.ceil(newScale * 10) / 10;
-    newScale = Math.min(MAX_SCALE, newScale);
+  zoomIn: function pdfViewZoomIn(ticks) {
+    var newScale = this.currentScale;
+    do {
+      newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2);
+      newScale = Math.ceil(newScale * 10) / 10;
+      newScale = Math.min(MAX_SCALE, newScale);
+    } while (--ticks && newScale < MAX_SCALE);
     this.parseScale(newScale, true);
   },
 
-  zoomOut: function pdfViewZoomOut() {
-    var newScale = (this.currentScale / DEFAULT_SCALE_DELTA).toFixed(2);
-    newScale = Math.floor(newScale * 10) / 10;
-    newScale = Math.max(MIN_SCALE, newScale);
+  zoomOut: function pdfViewZoomOut(ticks) {
+    var newScale = this.currentScale;
+    do {
+      newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2);
+      newScale = Math.floor(newScale * 10) / 10;
+      newScale = Math.max(MIN_SCALE, newScale);
+    } while (--ticks && newScale > MIN_SCALE);
     this.parseScale(newScale, true);
   },
 
   set page(val) {
     var pages = this.pages;
     var input = document.getElementById('pageNumber');
     var event = document.createEvent('UIEvents');
     event.initUIEvent('pagechange', false, false, window, 0);
@@ -1682,17 +1841,17 @@ var PDFView = {
 
     if (!this.pdfDocument) { // the PDF is not ready yet
       noData();
       return;
     }
 
     this.pdfDocument.getData().then(
       function getDataSuccess(data) {
-        var blob = PDFJS.createBlob(data.buffer, 'application/pdf');
+        var blob = PDFJS.createBlob(data, 'application/pdf');
         downloadManager.download(blob, url, filename);
       },
       noData // Error occurred try downloading with just the url.
     ).then(null, noData);
   },
 
   fallback: function pdfViewFallback() {
     // Only trigger the fallback once so we don't spam the user with messages
@@ -1934,16 +2093,18 @@ var PDFView = {
 
           if (self.pendingRefStr && self.pendingRefStr === refStr) {
             self.pendingRefStrLoaded.resolve();
           }
         });
         pagePromises.push(pagePromise);
       }
 
+      PDFFindController.firstPagePromise.resolve();
+
       PDFJS.Promise.all(pagePromises).then(function(pages) {
         pagesPromise.resolve(pages);
       });
     });
 
     var storePromise = store.initializedPromise;
     PDFJS.Promise.all([firstPagePromise, storePromise]).then(function() {
       var storedHash = null;
@@ -2265,26 +2426,18 @@ var PDFView = {
 
         if (outlineButton.getAttribute('disabled'))
           return;
         break;
     }
   },
 
   getVisiblePages: function pdfViewGetVisiblePages() {
-    if (!this.isPresentationMode) {
-      return this.getVisibleElements(this.container, this.pages, true);
-    } else {
-      // The algorithm in getVisibleElements is broken in presentation mode.
-      var visible = [], page = this.page;
-      var currentPage = this.pages[page - 1];
-      visible.push({ id: currentPage.id, view: currentPage });
-
-      return { first: currentPage, last: currentPage, views: visible};
-    }
+    return this.getVisibleElements(this.container, this.pages,
+                                   !this.isPresentationMode);
   },
 
   getVisibleThumbs: function pdfViewGetVisibleThumbs() {
     return this.getVisibleElements(this.thumbnailContainer, this.thumbnails);
   },
 
   // Generic helper to find out what elements are visible within a scroll pane.
   getVisibleElements: function pdfViewGetVisibleElements(
@@ -2413,26 +2566,32 @@ var PDFView = {
     return true;
   },
 
   enterPresentationMode: function pdfViewEnterPresentationMode() {
     this.isPresentationMode = true;
     this.page = this.presentationModeArgs.page;
     this.parseScale('page-fit', true);
     this.showPresentationControls();
+
+    var viewer = document.getElementById('viewer');
+    viewer.setAttribute('contextmenu', 'viewerContextMenu');
   },
 
   exitPresentationMode: function pdfViewExitPresentationMode() {
     this.isPresentationMode = false;
     this.parseScale(this.presentationModeArgs.previousScale);
     this.page = this.page;
     this.clearMouseScrollState();
     this.hidePresentationControls();
     this.presentationModeArgs = null;
 
+    var viewer = document.getElementById('viewer');
+    viewer.removeAttribute('contextmenu');
+
     // Ensure that the thumbnail of the current page is visible
     // when exiting presentation mode.
     scrollIntoView(document.getElementById('thumbnailContainer' + this.page));
   },
 
   showPresentationControls: function pdfViewShowPresentationControls() {
     var DELAY_BEFORE_HIDING_CONTROLS = 3000;
     var wrapper = document.getElementById('viewerContainer');
@@ -2563,16 +2722,18 @@ var PageView = function pageView(contain
   this.pdfPageRotate = defaultViewport.rotate;
 
   this.renderingState = RenderingStates.INITIAL;
   this.resume = null;
 
   this.textContent = null;
   this.textLayer = null;
 
+  this.annotationLayer = null;
+
   var anchor = document.createElement('a');
   anchor.name = '' + this.id;
 
   var div = this.el = document.createElement('div');
   div.id = 'pageContainer' + this.id;
   div.className = 'page';
   div.style.width = Math.floor(this.viewport.width) + 'px';
   div.style.height = Math.floor(this.viewport.height) + 'px';
@@ -2616,16 +2777,18 @@ var PageView = function pageView(contain
 
     div.style.width = Math.floor(this.viewport.width) + 'px';
     div.style.height = Math.floor(this.viewport.height) + 'px';
 
     while (div.hasChildNodes())
       div.removeChild(div.lastChild);
     div.removeAttribute('data-loaded');
 
+    this.annotationLayer = null;
+
     delete this.canvas;
 
     this.loadingIconDiv = document.createElement('div');
     this.loadingIconDiv.className = 'loadingIcon';
     div.appendChild(this.loadingIconDiv);
   };
 
   Object.defineProperty(this, 'width', {
@@ -2637,29 +2800,32 @@ var PageView = function pageView(contain
 
   Object.defineProperty(this, 'height', {
     get: function PageView_getHeight() {
       return this.viewport.height;
     },
     enumerable: true
   });
 
-  function setupAnnotations(annotationsDiv, pdfPage, viewport) {
+  var self = this;
+
+  function setupAnnotations(pageDiv, pdfPage, viewport) {
 
     function bindLink(link, dest) {
       link.href = PDFView.getDestinationHash(dest);
       link.onclick = function pageViewSetupLinksOnclick() {
         if (dest)
           PDFView.navigateTo(dest);
         return false;
       };
       link.className = 'internalLink';
     }
 
     function bindNamedAction(link, action) {
+      link.href = PDFView.getAnchorUrl('');
       link.onclick = function pageViewSetupNamedActionOnClick() {
         // See PDF reference, table 8.45 - Named action
         switch (action) {
           case 'GoToPage':
             document.getElementById('pageNumber').focus();
             break;
 
           case 'GoBack':
@@ -2696,16 +2862,22 @@ var PageView = function pageView(contain
             break; // No action according to spec
         }
         return false;
       };
       link.className = 'internalLink';
     }
 
     pdfPage.getAnnotations().then(function(annotationsData) {
+      if (self.annotationLayer) {
+        // If an annotationLayer already exists, delete it to avoid creating
+        // duplicate annotations when rapidly re-zooming the document.
+        pageDiv.removeChild(self.annotationLayer);
+        self.annotationLayer = null;
+      }
       viewport = viewport.clone({ dontFlip: true });
       for (var i = 0; i < annotationsData.length; i++) {
         var data = annotationsData[i];
         var annotation = PDFJS.Annotation.fromData(data);
         if (!annotation || !annotation.hasHtml()) {
           continue;
         }
 
@@ -2734,34 +2906,39 @@ var PageView = function pageView(contain
         if (data.subtype === 'Link' && !data.url) {
           if (data.action) {
             bindNamedAction(element, data.action);
           } else {
             bindLink(element, ('dest' in data) ? data.dest : null);
           }
         }
 
-        annotationsDiv.appendChild(element);
+        if (!self.annotationLayer) {
+          var annotationLayerDiv = document.createElement('div');
+          annotationLayerDiv.className = 'annotationLayer';
+          pageDiv.appendChild(annotationLayerDiv);
+          self.annotationLayer = annotationLayerDiv;
+        }
+        self.annotationLayer.appendChild(element);
       }
     });
   }
 
   this.getPagePoint = function pageViewGetPagePoint(x, y) {
     return this.viewport.convertToPdfPoint(x, y);
   };
 
   this.scrollIntoView = function pageViewScrollIntoView(dest) {
+      if (PDFView.isPresentationMode) { // Avoid breaking presentation mode.
+        dest = null;
+      }
       if (!dest) {
         scrollIntoView(div);
         return;
       }
-      if (PDFView.isPresentationMode) { // Avoid breaking presentation mode.
-        PDFView.page = id;
-        return;
-      }
 
       var x = 0, y = 0;
       var width = 0, height = 0, widthScale, heightScale;
       var scale = 0;
       switch (dest[1].name) {
         case 'XYZ':
           x = dest[2];
           y = dest[3];
@@ -2877,21 +3054,22 @@ var PageView = function pageView(contain
     if (!PDFJS.disableTextLayer) {
       textLayerDiv = document.createElement('div');
       textLayerDiv.className = 'textLayer';
       textLayerDiv.style.width = canvas.width + 'px';
       textLayerDiv.style.height = canvas.height + 'px';
       div.appendChild(textLayerDiv);
     }
     var textLayer = this.textLayer =
-          textLayerDiv ? new TextLayerBuilder({
-              textLayerDiv: textLayerDiv,
-              pageIndex: this.id - 1,
-              lastScrollSource: PDFView
-          }) : null;
+      textLayerDiv ? new TextLayerBuilder({
+        textLayerDiv: textLayerDiv,
+        pageIndex: this.id - 1,
+        lastScrollSource: PDFView,
+        isViewerInPresentationMode: PDFView.isPresentationMode
+      }) : null;
 
     if (outputScale.scaled) {
       var cssScale = 'scale(' + (1 / outputScale.sx) + ', ' +
                                 (1 / outputScale.sy) + ')';
       CustomStyle.setProp('transform' , canvas, cssScale);
       CustomStyle.setProp('transformOrigin' , canvas, '0% 0%');
       if (textLayerDiv) {
         CustomStyle.setProp('transform' , textLayerDiv, cssScale);
@@ -3263,16 +3441,17 @@ var TextLayerBuilder = function textLaye
   var textLayerFrag = document.createDocumentFragment();
 
   this.textLayerDiv = options.textLayerDiv;
   this.layoutDone = false;
   this.divContentDone = false;
   this.pageIdx = options.pageIndex;
   this.matches = [];
   this.lastScrollSource = options.lastScrollSource;
+  this.isViewerInPresentationMode = options.isViewerInPresentationMode;
 
   if(typeof PDFFindController === 'undefined') {
       window.PDFFindController = null;
   }
 
   if(typeof this.lastScrollSource === 'undefined') {
       this.lastScrollSource = null;
   }
@@ -3531,18 +3710,19 @@ var TextLayerBuilder = function textLaye
 
     for (i = i0; i < i1; i++) {
       var match = matches[i];
       var begin = match.begin;
       var end = match.end;
 
       var isSelected = isSelectedPage && i === selectedMatchIdx;
       var highlightSuffix = (isSelected ? ' selected' : '');
-      if (isSelected)
-        scrollIntoView(textDivs[begin.divIdx], {top: -50});
+      if (isSelected && !this.isViewerInPresentationMode) {
+        scrollIntoView(textDivs[begin.divIdx], { top: -50 });
+      }
 
       // Match inside new div.
       if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
         // If there was a previous div, then add the text at the end
         if (prevEnd !== null) {
           appendText(prevEnd, infty);
         }
         // clears the divs and set the content until the begin point.
@@ -3655,16 +3835,17 @@ var DocumentOutlineView = function docum
 
 document.addEventListener('DOMContentLoaded', function webViewerLoad(evt) {
   PDFView.initialize();
 
   var file = window.location.href.split('#')[0];
 
 
   document.getElementById('openFile').setAttribute('hidden', 'true');
+  document.getElementById('secondaryOpenFile').setAttribute('hidden', 'true');
 
   // Special debugging flags in the hash section of the URL.
   var hash = document.location.hash.substring(1);
   var hashParams = PDFView.parseQueryString(hash);
 
   if ('disableWorker' in hashParams) {
     PDFJS.disableWorker = (hashParams['disableWorker'] === 'true');
   }
@@ -3708,20 +3889,23 @@ document.addEventListener('DOMContentLoa
     var pdfBug = hashParams['pdfBug'];
     var enabled = pdfBug.split(',');
     PDFBug.enable(enabled);
     PDFBug.init();
   }
 
   if (!PDFView.supportsPrinting) {
     document.getElementById('print').classList.add('hidden');
+    document.getElementById('secondaryPrint').classList.add('hidden');
   }
 
   if (!PDFView.supportsFullscreen) {
     document.getElementById('presentationMode').classList.add('hidden');
+    document.getElementById('secondaryPresentationMode').
+      classList.add('hidden');
   }
 
   if (PDFView.supportsIntegratedFind) {
     document.getElementById('viewFind').classList.add('hidden');
   }
 
   // Listen for warnings to trigger the fallback UI.  Errors should be caught
   // and call PDFView.error() so we don't need to listen for those.
@@ -3779,32 +3963,16 @@ document.addEventListener('DOMContentLoa
       PDFView.zoomIn();
     });
 
   document.getElementById('zoomOut').addEventListener('click',
     function() {
       PDFView.zoomOut();
     });
 
-  document.getElementById('presentationMode').addEventListener('click',
-    function() {
-      PDFView.presentationMode();
-    });
-
-
-  document.getElementById('print').addEventListener('click',
-    function() {
-      window.print();
-    });
-
-  document.getElementById('download').addEventListener('click',
-    function() {
-      PDFView.download();
-    });
-
   document.getElementById('pageNumber').addEventListener('click',
     function() {
       this.select();
     });
 
   document.getElementById('pageNumber').addEventListener('change',
     function() {
       // Handle the user inputting a floating point number.
@@ -3815,35 +3983,39 @@ document.addEventListener('DOMContentLoa
       }
     });
 
   document.getElementById('scaleSelect').addEventListener('change',
     function() {
       PDFView.parseScale(this.value);
     });
 
-  document.getElementById('firstPage').addEventListener('click',
-    function() {
-      PDFView.page = 1;
-    });
-
-  document.getElementById('lastPage').addEventListener('click',
-    function() {
-      PDFView.page = PDFView.pdfDocument.numPages;
-    });
-
-  document.getElementById('pageRotateCcw').addEventListener('click',
-    function() {
-      PDFView.rotatePages(-90);
-    });
-
-  document.getElementById('pageRotateCw').addEventListener('click',
-    function() {
-      PDFView.rotatePages(90);
-    });
+  document.getElementById('presentationMode').addEventListener('click',
+    SecondaryToolbar.presentationModeClick.bind(SecondaryToolbar));
+
+  document.getElementById('openFile').addEventListener('click',
+    SecondaryToolbar.openFileClick.bind(SecondaryToolbar));
+
+  document.getElementById('print').addEventListener('click',
+    SecondaryToolbar.printClick.bind(SecondaryToolbar));
+
+  document.getElementById('download').addEventListener('click',
+    SecondaryToolbar.downloadClick.bind(SecondaryToolbar));
+
+  document.getElementById('contextFirstPage').addEventListener('click',
+    SecondaryToolbar.firstPageClick.bind(SecondaryToolbar));
+
+  document.getElementById('contextLastPage').addEventListener('click',
+    SecondaryToolbar.lastPageClick.bind(SecondaryToolbar));
+
+  document.getElementById('contextPageRotateCw').addEventListener('click',
+    SecondaryToolbar.pageRotateCwClick.bind(SecondaryToolbar));
+
+  document.getElementById('contextPageRotateCcw').addEventListener('click',
+    SecondaryToolbar.pageRotateCcwClick.bind(SecondaryToolbar));
 
   PDFView.setTitleUsingUrl(file);
   PDFView.initPassiveLoading();
   return;
 
   PDFView.open(file, 0);
 }, true);
 
@@ -3874,21 +4046,19 @@ function updateViewarea() {
       break;
     }
   }
 
   if (!stillFullyVisible) {
     currentId = visiblePages[0].id;
   }
 
-  if (!PDFView.isPresentationMode) {
-    updateViewarea.inProgress = true; // used in "set page"
-    PDFView.page = currentId;
-    updateViewarea.inProgress = false;
-  }
+  updateViewarea.inProgress = true; // used in "set page"
+  PDFView.page = currentId;
+  updateViewarea.inProgress = false;
 
   var currentScale = PDFView.currentScale;
   var currentScaleValue = PDFView.currentScaleValue;
   var normalizedScaleValue = currentScaleValue == currentScale ?
     currentScale * 100 : currentScaleValue;
 
   var pageNumber = firstPage.id;
   var pdfOpenParams = '#page=' + pageNumber;
@@ -3916,16 +4086,19 @@ function updateViewarea() {
 window.addEventListener('resize', function webViewerResize(evt) {
   if (PDFView.initialized &&
       (document.getElementById('pageWidthOption').selected ||
        document.getElementById('pageFitOption').selected ||
        document.getElementById('pageAutoOption').selected)) {
     PDFView.parseScale(document.getElementById('scaleSelect').value);
   }
   updateViewarea();
+
+  // Set the 'max-height' CSS property of the secondary toolbar.
+  SecondaryToolbar.setMaxHeight(PDFView.container);
 });
 
 window.addEventListener('hashchange', function webViewerHashchange(evt) {
   if (PDFHistory.isHashChangeUnlocked) {
     PDFView.setHash(document.location.hash.substring(1));
   }
 });
 
@@ -3944,16 +4117,17 @@ window.addEventListener('change', functi
 
   var file = files[0];
   fileReader.readAsArrayBuffer(file);
   PDFView.setTitleUsingUrl(file.name);
 
   // URL does not reflect proper document location - hiding some icons.
   document.getElementById('viewBookmark').setAttribute('hidden', 'true');
   document.getElementById('download').setAttribute('hidden', 'true');
+  document.getElementById('secondaryDownload').setAttribute('hidden', 'true');
 }, true);
 
 function selectScaleOption(value) {
   var options = document.getElementById('scaleSelect').options;
   var predefinedValueFound = false;
   for (var i = 0; i < options.length; i++) {
     var option = options[i];
     if (option.value != value) {
@@ -3964,30 +4138,33 @@ function selectScaleOption(value) {
     predefinedValueFound = true;
   }
   return predefinedValueFound;
 }
 
 window.addEventListener('localized', function localized(evt) {
   document.getElementsByTagName('html')[0].dir = mozL10n.getDirection();
 
-  // Adjust the width of the zoom box to fit the content.
-  // Note: This is only done if the zoom box is actually visible,
-  // since otherwise element.clientWidth will return 0.
   PDFView.animationStartedPromise.then(function() {
+    // Adjust the width of the zoom box to fit the content.
+    // Note: This is only done if the zoom box is actually visible,
+    // since otherwise element.clientWidth will return 0.
     var container = document.getElementById('scaleSelectContainer');
     if (container.clientWidth > 0) {
       var select = document.getElementById('scaleSelect');
       select.setAttribute('style', 'min-width: inherit;');
       var width = select.clientWidth + SCALE_SELECT_CONTAINER_PADDING;
       select.setAttribute('style', 'min-width: ' +
                                    (width + SCALE_SELECT_PADDING) + 'px;');
       container.setAttribute('style', 'min-width: ' + width + 'px; ' +
                                       'max-width: ' + width + 'px;');
     }
+
+    // Set the 'max-height' CSS property of the secondary toolbar.
+    SecondaryToolbar.setMaxHeight(PDFView.container);
   });
 }, true);
 
 window.addEventListener('scalechange', function scalechange(evt) {
   document.getElementById('zoomOut').disabled = (evt.scale === MIN_SCALE);
   document.getElementById('zoomIn').disabled = (evt.scale === MAX_SCALE);
 
   var customScaleOption = document.getElementById('customScaleOption');
@@ -4037,18 +4214,17 @@ window.addEventListener('pagechange', fu
 
 // Firefox specific event, so that we can prevent browser from zooming
 window.addEventListener('DOMMouseScroll', function(evt) {
   if (evt.ctrlKey) {
     evt.preventDefault();
 
     var ticks = evt.detail;
     var direction = (ticks > 0) ? 'zoomOut' : 'zoomIn';
-    for (var i = 0, length = Math.abs(ticks); i < length; i++)
-      PDFView[direction]();
+    PDFView[direction](Math.abs(ticks));
   } else if (PDFView.isPresentationMode) {
     var FIREFOX_DELTA_FACTOR = -40;
     PDFView.mouseScroll(evt.detail * FIREFOX_DELTA_FACTOR);
   }
 }, false);
 
 window.addEventListener('mousemove', function mousemove(evt) {
   if (PDFView.isPresentationMode) {
@@ -4068,19 +4244,23 @@ window.addEventListener('mousedown', fun
       // mode.
       evt.preventDefault();
       PDFView.page++;
     }
   }
 }, false);
 
 window.addEventListener('click', function click(evt) {
-  if (PDFView.isPresentationMode && evt.button === 0) {
+  if (!PDFView.isPresentationMode) {
+    if (SecondaryToolbar.isOpen && PDFView.container.contains(evt.target)) {
+      SecondaryToolbar.close();
+    }
+  } else if (evt.button === 0) {
     // Necessary since preventDefault() in 'mousedown' won't stop
-    // the event propagation in all circumstances.
+    // the event propagation in all circumstances in presentation mode.
     evt.preventDefault();
   }
 }, false);
 
 window.addEventListener('keydown', function keydown(evt) {
   var handled = false;
   var cmd = (evt.ctrlKey ? 1 : 0) |
             (evt.altKey ? 2 : 0) |
@@ -4129,32 +4309,37 @@ window.addEventListener('keydown', funct
     }
   }
 
   // CTRL+ALT or Option+Command
   if (cmd === 3 || cmd === 10) {
     switch (evt.keyCode) {
       case 80: // p
         PDFView.presentationMode();
+        SecondaryToolbar.close();
         handled = true;
         break;
     }
   }
 
   if (handled) {
     evt.preventDefault();
     return;
   }
 
   // Some shortcuts should not get handled if a control/input element
   // is selected.
   var curElement = document.activeElement || document.querySelector(':focus');
   if (curElement && (curElement.tagName.toUpperCase() === 'INPUT' ||
+                     curElement.tagName.toUpperCase() === 'TEXTAREA' ||
                      curElement.tagName.toUpperCase() === 'SELECT')) {
-    return;
+    // Make sure that the secondary toolbar is closed when Escape is pressed.
+    if (evt.keyCode !== 27) { // 'Esc'
+      return;
+    }
   }
   var controlsElement = document.getElementById('toolbar');
   while (curElement) {
     if (curElement === controlsElement && !PDFView.isPresentationMode)
       return; // ignoring if the 'toolbar' element is focused
     curElement = curElement.parentNode;
   }
 
@@ -4176,16 +4361,20 @@ window.addEventListener('keydown', funct
         }
         /* falls through */
       case 75: // 'k'
       case 80: // 'p'
         PDFView.page--;
         handled = true;
         break;
       case 27: // esc key
+        if (SecondaryToolbar.isOpen) {
+          SecondaryToolbar.close();
+          handled = true;
+        }
         if (!PDFView.supportsIntegratedFind && PDFFindBar.opened) {
           PDFFindBar.close();
           handled = true;
         }
         break;
       case 40: // down arrow
       case 34: // pg down
       case 32: // spacebar
--- a/browser/extensions/pdfjs/extension-files
+++ b/browser/extensions/pdfjs/extension-files
@@ -16,29 +16,34 @@ content/web/images/annotation-newparagra
 content/web/images/annotation-note.svg
 content/web/images/annotation-paragraph.svg
 content/web/images/findbarButton-next-rtl.png
 content/web/images/findbarButton-next.png
 content/web/images/findbarButton-previous-rtl.png
 content/web/images/findbarButton-previous.png
 content/web/images/loading-icon.gif
 content/web/images/loading-small.png
+content/web/images/secondaryToolbarButton-firstPage.png
+content/web/images/secondaryToolbarButton-lastPage.png
+content/web/images/secondaryToolbarButton-rotateCcw.png
+content/web/images/secondaryToolbarButton-rotateCw.png
 content/web/images/shadow.png
 content/web/images/texture.png
 content/web/images/toolbarButton-bookmark.png
 content/web/images/toolbarButton-download.png
 content/web/images/toolbarButton-menuArrows.png
 content/web/images/toolbarButton-openFile.png
 content/web/images/toolbarButton-pageDown-rtl.png
 content/web/images/toolbarButton-pageDown.png
 content/web/images/toolbarButton-pageUp-rtl.png
 content/web/images/toolbarButton-pageUp.png
 content/web/images/toolbarButton-presentationMode.png
 content/web/images/toolbarButton-print.png
 content/web/images/toolbarButton-search.png
+content/web/images/toolbarButton-secondaryToolbarToggle.png
 content/web/images/toolbarButton-sidebarToggle.png
 content/web/images/toolbarButton-viewOutline.png
 content/web/images/toolbarButton-viewThumbnail.png
 content/web/images/toolbarButton-zoomIn.png
 content/web/images/toolbarButton-zoomOut.png
 content/web/l10n.js
 content/web/viewer.css
 content/web/viewer.html
--- a/browser/locales/en-US/chrome/browser/devtools/app-manager.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/app-manager.dtd
@@ -1,15 +1,15 @@
 <!-- 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/. -->
 
 <!ENTITY index.title "App Manager">
-<!ENTITY index.projects "My Apps">
-<!ENTITY index.device "My Device">
+<!ENTITY index.projects2 "Apps">
+<!ENTITY index.device2 "Device">
 
 <!ENTITY device.screenshot "Screenshot">
 <!ENTITY device.title "Device Control Center">
 <!ENTITY device.notConnected "Not connected. Please connect your device below.">
 <!ENTITY device.startApp "Start">
 <!ENTITY device.stopApp "Stop">
 <!ENTITY device.debugApp "Debug">
 <!ENTITY device.name "Name">
--- a/browser/locales/en-US/chrome/browser/devtools/app-manager.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/app-manager.properties
@@ -6,17 +6,17 @@
 # the device's height, %3$S is the device's pixel density.
 # Example: 800x480 (86 DPI).
 device.deviceSize=Device size: %1$Sx%2$S (%3$S DPI)
 # LOCALIZATION NOTE (connection.connectedToDevice, connection.connectTo):
 # %1$S is the host name, %2$S is the port number.
 connection.connectedToDevice=Connected to %1$S
 connection.connectTo=Connect to %1$S:%2$S
 project.filePickerTitle=Select a webapp folder
-project.installing=Installing...
+project.installing=Installing…
 project.installed=Installed!
 validator.nonExistingFolder=The project folder doesn't exists
 validator.expectProjectFolder=The project folder ends up being a file
 validator.wrongManifestFileName=Packaged apps require a manifest file that can only be named 'manifest.webapp' at project root folder
 validator.invalidManifestURL=Invalid manifest URL '%S'
 # LOCALIZATION NOTE (validator.invalidManifestJSON, validator.noAccessManifestURL):
 # %1$S is the error message, %2$S is the URI of the manifest.
 validator.invalidManifestJSON=The webapp manifest isn't a valid JSON file: %1$S at: %2$S
--- a/browser/locales/en-US/pdfviewer/viewer.properties
+++ b/browser/locales/en-US/pdfviewer/viewer.properties
@@ -25,27 +25,43 @@ next_label=Next
 page_label=Page:
 page_of=of {{pageCount}}
 
 zoom_out.title=Zoom Out
 zoom_out_label=Zoom Out
 zoom_in.title=Zoom In
 zoom_in_label=Zoom In
 zoom.title=Zoom
-print.title=Print
-print_label=Print
 presentation_mode.title=Switch to Presentation Mode
 presentation_mode_label=Presentation Mode
 open_file.title=Open File
-open_file_label=Open
+open_file_label=Open File
+print.title=Print
+print_label=Print
 download.title=Download
 download_label=Download
 bookmark.title=Current view (copy or open in new window)
 bookmark_label=Current View
 
+# Secondary toolbar and context menu
+tools.title=Tools
+tools_label=Tools
+first_page.title=Go to First Page
+first_page.label=Go to First Page
+first_page_label=Go to First Page
+last_page.title=Go to Last Page
+last_page.label=Go to Last Page
+last_page_label=Go to Last Page
+page_rotate_cw.title=Rotate Clockwise
+page_rotate_cw.label=Rotate Clockwise
+page_rotate_cw_label=Rotate Clockwise
+page_rotate_ccw.title=Rotate Counterclockwise
+page_rotate_ccw.label=Rotate Counterclockwise
+page_rotate_ccw_label=Rotate Counterclockwise
+
 # Tooltips and alt text for side panel toolbar buttons
 # (the _label strings are alt text for the buttons, the .title strings are
 # tooltips)
 toggle_sidebar.title=Toggle Sidebar
 toggle_sidebar_label=Toggle Sidebar
 outline.title=Show Document Outline
 outline_label=Document Outline
 thumbs.title=Show Thumbnails
@@ -56,22 +72,16 @@ findbar_label=Find
 # Thumbnails panel item (tooltip and alt text for images)
 # LOCALIZATION NOTE (thumb_page_title): "{{page}}" will be replaced by the page
 # number.
 thumb_page_title=Page {{page}}
 # LOCALIZATION NOTE (thumb_page_canvas): "{{page}}" will be replaced by the page
 # number.
 thumb_page_canvas=Thumbnail of Page {{page}}
 
-# Context menu
-first_page.label=Go to First Page
-last_page.label=Go to Last Page
-page_rotate_cw.label=Rotate Clockwise
-page_rotate_ccw.label=Rotate Counterclockwise
-
 # Find panel button title and messages
 find_label=Find:
 find_previous.title=Find the previous occurrence of the phrase
 find_previous_label=Previous
 find_next.title=Find the next occurrence of the phrase
 find_next_label=Next
 find_highlight=Highlight all
 find_match_case_label=Match case
--- a/browser/metro/base/content/flyoutpanels/SyncFlyoutPanel.js
+++ b/browser/metro/base/content/flyoutpanels/SyncFlyoutPanel.js
@@ -103,20 +103,20 @@ let SyncFlyoutPanel = {
   },
 
   _onSyncEnd: function() {
     this._isSyncing = false;
     this._updateConnectedPage();
   },
 
   showInitialScreen: function() {
-    if (Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED) {
+    if (Weave.Status.login == Weave.LOGIN_SUCCEEDED) {
+      this.showConnected();
+    } else {
       this.showPreSetup();
-    } else {
-      this.showConnected();
     }
   },
 
   abortEasySetup: function() {
     if (this._setupJpake) {
       this._setupJpake.abort();
     }
     this._cleanUpEasySetup();
--- a/browser/themes/shared/devtools/dark-theme.css
+++ b/browser/themes/shared/devtools/dark-theme.css
@@ -43,17 +43,17 @@
   background-position: -42px 0;
 }
 
 .theme-selected {
   background: #26394D;
 }
 
 .theme-bg-darker {
-  background-color: rgba(0,0,0,0.1);
+  background-color: rgba(0,0,0,0.5);
 }
 
 .theme-link { /* blue */
   color: #3689b2;
 }
 
 .theme-comment { /* grey */
   color: #5c6773;
--- a/browser/themes/shared/devtools/light-theme.css
+++ b/browser/themes/shared/devtools/light-theme.css
@@ -43,17 +43,17 @@
   background-position: -14px 0;
 }
 
 .theme-selected {
   background-color: #CCC;
 }
 
 .theme-bg-darker {
-  background: #F9F9F9;
+  background: #EFEFEF;
 }
 
 .theme-link { /* blue */
   color: hsl(208,56%,40%);
 }
 
 .theme-comment { /* grey */
   color: hsl(90,2%,46%);
--- a/browser/themes/shared/devtools/markup-view.css
+++ b/browser/themes/shared/devtools/markup-view.css
@@ -2,37 +2,15 @@
  * 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/. */
 
 * {
   padding: 0;
   margin: 0;
 }
 
-.newattr {
-  cursor: pointer;
-  padding: 1px 0;
-}
-
-li.container {
-  padding: 2px 0 0 2px;
-}
-
-.codebox {
-  padding-left: 14px;
-}
-
-.codebox > * {
-  vertical-align: middle;
-}
-
-.expander {
-  display: inline-block;
-  margin-left: -14px;
-}
-
 .more-nodes {
   padding-left: 16px;
 }
 
 .styleinspector-propertyeditor {
   border: 1px solid #CCC;
 }
--- a/browser/themes/windows/browser-aero.css
+++ b/browser/themes/windows/browser-aero.css
@@ -323,22 +323,16 @@
   #navigator-toolbox[tabsontop=false] .searchbar-textbox:not(:-moz-lwtheme)[focused] {
     background-color: white;
   }
 
   .tabbrowser-tab:not(:-moz-lwtheme) {
     text-shadow: none;
   }
 
-  #main-window[sizemode=normal] .statuspanel-inner {
-    /* align with the browser's side borders */
-    padding-left: 1px;
-    padding-right: 1px;
-  }
-
   #ctrlTab-panel {
     background: transparent;
     -moz-appearance: -moz-win-glass;
     border-radius: 0;
     border: none;
     font: normal 1.2em "Segoe UI";
     color: black;
     text-shadow: white -1px -1px .35em, white -1px 1px .35em, white 1px 1px .35em, white 1px -1px .35em;
--- a/build/pymake/pymake/parser.py
+++ b/build/pymake/pymake/parser.py
@@ -369,42 +369,62 @@ def parsefile(pathname):
     makefiles that have already been parsed and have not changed.
     """
 
     pathname = os.path.realpath(pathname)
     return _parsecache.get(pathname)
 
 # colon followed by anything except a slash (Windows path detection)
 _depfilesplitter = re.compile(r':(?![\\/])')
+# simple variable references
+_vars = re.compile('\$\((\w+)\)')
 
 def parsedepfile(pathname):
     """
     Parse a filename listing only depencencies into a parserdata.StatementList.
+    Simple variable references are allowed in such files.
     """
     def continuation_iter(lines):
         current_line = []
         for line in lines:
             line = line.rstrip()
             if line.endswith("\\"):
                 current_line.append(line.rstrip("\\"))
                 continue
             if not len(line):
                 continue
             current_line.append(line)
             yield ''.join(current_line)
             current_line = []
         if current_line:
             yield ''.join(current_line)
 
+    def get_expansion(s):
+        if '$' in s:
+            expansion = data.Expansion()
+            # for an input like e.g. "foo $(bar) baz",
+            # _vars.split returns ["foo", "bar", "baz"]
+            # every other element is a variable name.
+            for i, element in enumerate(_vars.split(s)):
+                if i % 2:
+                    expansion.appendfunc(functions.VariableRef(None,
+                        data.StringExpansion(element, None)))
+                elif element:
+                    expansion.appendstr(element)
+
+            return expansion
+
+        return data.StringExpansion(s, None)
+
     pathname = os.path.realpath(pathname)
     stmts = parserdata.StatementList()
     for line in continuation_iter(open(pathname).readlines()):
         target, deps = _depfilesplitter.split(line, 1)
-        stmts.append(parserdata.Rule(data.StringExpansion(target, None),
-                                     data.StringExpansion(deps, None), False))
+        stmts.append(parserdata.Rule(get_expansion(target),
+                                     get_expansion(deps), False))
     return stmts
 
 def parsestring(s, filename):
     """
     Parse a string containing makefile data into a parserdata.StatementList.
     """
 
     currule = False
new file mode 100644
--- /dev/null
+++ b/build/pymake/tests/includedeps-variables.deps
@@ -0,0 +1,1 @@
+$(FILE)1: filemissing
new file mode 100644
--- /dev/null
+++ b/build/pymake/tests/includedeps-variables.mk
@@ -0,0 +1,10 @@
+#T gmake skip
+
+FILE = includedeps-variables
+
+all: $(FILE)1
+
+includedeps $(TESTPATH)/includedeps-variables.deps
+
+filemissing:
+	@echo TEST-PASS
--- a/build/unix/elfhack/Makefile.in
+++ b/build/unix/elfhack/Makefile.in
@@ -5,21 +5,16 @@
 
 INTERNAL_TOOLS = 1
 
 HOST_PROGRAM = elfhack
 NO_PROFILE_GUIDED_OPTIMIZE = 1
 
 VPATH += $(topsrcdir)/build
 
-HOST_CPPSRCS = \
-  elf.cpp \
-  elfhack.cpp \
-  $(NULL)
-
 OS_CXXFLAGS := $(filter-out -fno-exceptions,$(OS_CXXFLAGS)) -fexceptions
 
 ifndef CROSS_COMPILE
 CSRCS += dummy.c
 endif
 
 WRAP_LDFLAGS=
 
--- a/build/unix/elfhack/moz.build
+++ b/build/unix/elfhack/moz.build
@@ -7,8 +7,12 @@
 NO_DIST_INSTALL = True
 DIRS += ['inject']
 
 CSRCS += [
     'test-array.c',
     'test-ctors.c',
 ]
 
+HOST_CPPSRCS += [
+    'elf.cpp',
+    'elfhack.cpp',
+]
--- a/build/unix/stdc++compat/Makefile.in
+++ b/build/unix/stdc++compat/Makefile.in
@@ -2,16 +2,12 @@
 # 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/.
 
 FORCE_STATIC_LIB= 1
 STL_FLAGS =
 NO_EXPAND_LIBS = 1
 NO_PROFILE_GUIDED_OPTIMIZE = 1
 
-ifdef MOZ_LIBSTDCXX_HOST_VERSION
-HOST_CPPSRCS = stdc++compat.cpp
-endif
-
 include $(topsrcdir)/config/rules.mk
 
 CXXFLAGS += -DMOZ_LIBSTDCXX_VERSION=$(MOZ_LIBSTDCXX_TARGET_VERSION)
 HOST_CXXFLAGS += -DMOZ_LIBSTDCXX_VERSION=$(MOZ_LIBSTDCXX_HOST_VERSION)
--- a/build/unix/stdc++compat/moz.build
+++ b/build/unix/stdc++compat/moz.build
@@ -7,9 +7,12 @@
 MODULE = 'build'
 
 if CONFIG['MOZ_LIBSTDCXX_TARGET_VERSION']:
     LIBRARY_NAME = 'stdc++compat'
     CPP_SOURCES += ['stdc++compat.cpp']
 
 if CONFIG['MOZ_LIBSTDCXX_HOST_VERSION']:
     HOST_LIBRARY_NAME = 'host_stdc++compat'
+    HOST_CPPSRCS += [
+        'stdc++compat.cpp',
+    ]
 
--- a/caps/tests/mochitest/test_bug292789.html
+++ b/caps/tests/mochitest/test_bug292789.html
@@ -7,50 +7,50 @@ https://bugzilla.mozilla.org/show_bug.cg
   <title>Test for Bug 292789</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=292789">Mozilla Bug 292789</a>
 <p id="display"></p>
 <div id="content" style="display: none">
-  <script src="chrome://global/content/strres.js"></script>
+  <script src="chrome://global/content/treeUtils.js"></script>
   <script type="application/javascript;version=1.8" src="chrome://mozapps/content/xpinstall/xpinstallConfirm.js"></script>
   <script id="resjs" type="application/javascript;version=1.8"></script>
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Bug 292789
  **
  ** Selectively allow access to whitelisted chrome packages
  ** even for ALLOW_CHROME mechanisms (<script>, <img> etc)
  **/
 
 SimpleTest.waitForExplicitFinish();
 
 /** <script src=""> test **/
 function testScriptSrc(aCallback) {
-    is(typeof srGetStrBundle, "function",
+    is(typeof gTreeUtils.sort, "function",
        "content can still load <script> from chrome://global");
     is(typeof XPInstallConfirm, "undefined",
        "content should not be able to load <script> from chrome://mozapps");
-    
+
     /** make sure the last one didn't pass because someone
      ** moved the resource
      **/
     var resjs = document.getElementById("resjs");
     resjs.onload = scriptOnload;
     resjs.src = "resource://gre/chrome/toolkit/content/mozapps/xpinstall/xpinstallConfirm.js";
     document.getElementById("content").appendChild(resjs);
 
     function scriptOnload() {
       is(typeof XPInstallConfirm, "object",
          "xpinstallConfirm.js has not moved unexpectedly");
-  
+
       // trigger the callback
       if (aCallback)
         aCallback();
     }
 }
 
 /** <img src=""> tests **/
 var img_global = "chrome://global/skin/icons/Error.png";
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -1482,17 +1482,17 @@ endif
 
 ifneq ($(DIST_CHROME_FILES),)
 DIST_CHROME_FILES_PATH := $(FINAL_TARGET)/chrome
 DIST_CHROME_FILES_FLAGS := $(XULAPP_DEFINES)
 PP_TARGETS += DIST_CHROME_FILES
 endif
 
 ifneq ($(XPI_PKGNAME),)
-libs realchrome::
+tools realchrome::
 ifdef STRIP_XPI
 ifndef MOZ_DEBUG
 	@echo "Stripping $(XPI_PKGNAME) package directory..."
 	@echo $(FINAL_TARGET)
 	@cd $(FINAL_TARGET) && find . ! -type d \
 			! -name "*.js" \
 			! -name "*.xpt" \
 			! -name "*.gif" \
@@ -1521,17 +1521,17 @@ endif
 	cd $(FINAL_TARGET) && $(ZIP) -qr ../$(XPI_PKGNAME).xpi *
 endif
 
 ifdef INSTALL_EXTENSION_ID
 ifndef XPI_NAME
 $(error XPI_NAME must be set for INSTALL_EXTENSION_ID)
 endif
 
-libs::
+tools::
 	$(RM) -r "$(DIST)/bin$(DIST_SUBDIR:%=/%)/extensions/$(INSTALL_EXTENSION_ID)"
 	$(NSINSTALL) -D "$(DIST)/bin$(DIST_SUBDIR:%=/%)/extensions/$(INSTALL_EXTENSION_ID)"
 	$(call copy_dir,$(FINAL_TARGET),$(DIST)/bin$(DIST_SUBDIR:%=/%)/extensions/$(INSTALL_EXTENSION_ID))
 
 endif
 
 #############################################################################
 # MDDEPDIR is the subdirectory where all the dependency files are placed.
--- a/content/base/public/nsIContentSecurityPolicy.idl
+++ b/content/base/public/nsIContentSecurityPolicy.idl
@@ -5,86 +5,114 @@
 #include "nsISupports.idl"
 
 interface nsIURI;
 interface nsIHttpChannel;
 interface nsIDocShell;
 
 /**
  * nsIContentSecurityPolicy
- * Describes an XPCOM component used to model an enforce CSPs.
+ * Describes an XPCOM component used to model and enforce CSPs.  Instances of
+ * this class may have multiple policies within them, but there should only be
+ * one of these per document/principal.
  */
 
-[scriptable, uuid(230b126d-afc3-4588-9794-3e135594d626)]
+[scriptable, uuid(e5020ec3-1437-46f5-b4eb-8b60766d02c0)]
 interface nsIContentSecurityPolicy : nsISupports
 {
 
   /**
    * Set to true when the CSP has been read in and parsed and is ready to
    * enforce.  This is a barrier for the nsDocument so it doesn't load any
    * sub-content until either it knows that a CSP is ready or will not be used.
    */
   attribute boolean isInitialized;
 
   /**
-   * When set to true, content load-blocking and fail-closed are disabled: CSP
-   * will ONLY send reports, and not modify behavior.
+   * Accessor method for a read-only string version of the policy at a given
+   * index.
    */
-  attribute boolean reportOnlyMode;
+  AString getPolicy(in unsigned long index);
+
+  /**
+   * Returns the number of policies attached to this CSP instance.  Useful with
+   * getPolicy().
+   */
+  attribute long policyCount;
 
   /**
-   * A read-only string version of the policy for debugging.
+   * Remove a policy associated with this CSP context.
+   * @throws NS_ERROR_FAILURE if the index is out of bounds or invalid.
    */
-  readonly attribute AString policy;
+  void removePolicy(in unsigned long index);
+
+  /**
+   * Parse and install a CSP policy.
+   * @param aPolicy
+   *        String representation of the policy (e.g., header value)
+   * @param selfURI
+   *        the URI of the protected document/principal
+   * @param reportOnly
+   *        Should this policy affect content, script and style processing or
+   *        just send reports if it is violated?
+   * @param specCompliant
+   *        Whether or not the policy conforms to the W3C specification.
+   *        If this is false, that indicates this policy is from the older
+   *        implementation with different semantics and directive names.
+   */
+  void appendPolicy(in AString policyString, in nsIURI selfURI,
+                    in boolean reportOnly, in boolean specCompliant);
 
   /**
    * Whether this policy allows in-page script.
-   * @param shouldReportViolation
+   * @param shouldReportViolations
    *     Whether or not the use of inline script should be reported.
    *     This function always returns "true" for report-only policies, but when
-   *     the report-only policy is violated, shouldReportViolation is true as
-   *     well.
+   *     any policy (report-only or otherwise) is violated,
+   *     shouldReportViolations is true as well.
    * @return
    *     Whether or not the effects of the inline script should be allowed
    *     (block the compilation if false).
    */
-  boolean getAllowsInlineScript(out boolean shouldReportViolation);
+  boolean getAllowsInlineScript(out boolean shouldReportViolations);
 
   /**
    * whether this policy allows eval and eval-like functions
    * such as setTimeout("code string", time).
-   * @param shouldReportViolation
+   * @param shouldReportViolations
    *     Whether or not the use of eval should be reported.
-   *     This function always returns "true" for report-only policies, but when
-   *     the report-only policy is violated, shouldReportViolation is true as
-   *     well.
+   *     This function returns "true" when violating report-only policies, but
+   *     when any policy (report-only or otherwise) is violated,
+   *     shouldReportViolations is true as well.
    * @return
    *     Whether or not the effects of the eval call should be allowed
    *     (block the call if false).
    */
-  boolean getAllowsEval(out boolean shouldReportViolation);
+  boolean getAllowsEval(out boolean shouldReportViolations);
 
   /**
    * Whether this policy allows in-page styles.
    * This includes <style> tags with text content and style="" attributes in
    * HTML elements.
-   * @param shouldReportViolation
-   *     Whether or not the use of eval should be reported.
-   *     This function always returns "true" for report-only policies, but when
-   *     the report-only policy is violated, shouldReportViolation is true as
-   *     well.
+   * @param shouldReportViolations
+   *     Whether or not the use of inline style should be reported.
+   *     If there are report-only policies, this function may return true
+   *     (don't block), but one or more policy may still want to send
+   *     violation reports so shouldReportViolations will be true even if the
+   *     inline style should be permitted.
    * @return
-   *     Whether or not the effects of the eval call should be allowed
-   *     (block the call if false).
+   *     Whether or not the effects of the inline style should be allowed
+   *     (block the rules if false).
    */
-  boolean getAllowsInlineStyle(out boolean shouldReportViolation);
+  boolean getAllowsInlineStyle(out boolean shouldReportViolations);
 
   /**
-   * Log policy violation on the Error Console and send a report if a report-uri
-   * is present in the policy
+   * For each violated policy (of type violationType), log policy violation on
+   * the Error Console and send a report to report-uris present in the violated
+   * policies.
    *
    * @param violationType
    *     one of the VIOLATION_TYPE_* constants, e.g. inline-script or eval
    * @param sourceFile
    *     name of the source file containing the violation (if available)
    * @param contentSample
    *     sample of the violating content (to aid debugging)
    * @param lineNum
@@ -95,79 +123,56 @@ interface nsIContentSecurityPolicy : nsI
                            in AString scriptSample,
                            in int32_t lineNum);
 
   const unsigned short VIOLATION_TYPE_INLINE_SCRIPT = 1;
   const unsigned short VIOLATION_TYPE_EVAL = 2;
   const unsigned short VIOLATION_TYPE_INLINE_STYLE = 3;
 
   /**
-   * Manually triggers violation report sending given a URI and reason.
-   * The URI may be null, in which case "self" is sent.
-   * @param blockedURI
-   *     the URI that violated the policy
-   * @param violatedDirective
-   *     the directive that was violated.
-   * @param scriptSample
-   *     a sample of the violating inline script
-   * @param lineNum
-   *     source line number of the violation (if available)
-   * @return
-   *     nothing.
-   */
-  void sendReports(in AString blockedURI,
-                   in AString violatedDirective,
-                   in AString scriptSample,
-                   in int32_t lineNum);
-
-  /**
    * Called after the CSP object is created to fill in the appropriate request
    * and request header information needed in case a report needs to be sent.
    */
   void scanRequestData(in nsIHttpChannel aChannel);
 
   /**
-   * Updates the policy currently stored in the CSP to be "refined" or
-   * tightened by the one specified in the string policyString.
-   */
-  void refinePolicy(in AString policyString, in nsIURI selfURI, in boolean specCompliant);
-
-  /**
    * Verifies ancestry as permitted by the policy.
    *
-   * Calls to this may trigger violation reports when queried, so
-   * this value should not be cached.
+   * NOTE: Calls to this may trigger violation reports when queried, so this
+   * value should not be cached.
    *
    * @param docShell
    *    containing the protected resource
    * @return
-   *    true if the frame's ancestors are all permitted by policy
+   *    true if the frame's ancestors are all allowed by policy (except for
+   *    report-only policies, which will send reports and then return true
+   *    here when violated).
    */
   boolean permitsAncestry(in nsIDocShell docShell);
 
   /**
    * Delegate method called by the service when sub-elements of the protected
    * document are being loaded.  Given a bit of information about the request,
    * decides whether or not the policy is satisfied.
    *
    * Calls to this may trigger violation reports when queried, so
    * this value should not be cached.
    */
-  short shouldLoad(in unsigned long   aContentType, 
-                   in nsIURI          aContentLocation, 
-                   in nsIURI          aRequestOrigin, 
-                   in nsISupports     aContext, 
-                   in ACString        aMimeTypeGuess, 
+  short shouldLoad(in unsigned long   aContentType,
+                   in nsIURI          aContentLocation,
+                   in nsIURI          aRequestOrigin,
+                   in nsISupports     aContext,
+                   in ACString        aMimeTypeGuess,
                    in nsISupports     aExtra);
 
   /**
    * Delegate method called by the service when sub-elements of the protected
    * document are being processed.  Given a bit of information about the request,
    * decides whether or not the policy is satisfied.
    */
-  short shouldProcess(in unsigned long   aContentType, 
-                      in nsIURI          aContentLocation, 
-                      in nsIURI          aRequestOrigin, 
-                      in nsISupports     aContext, 
+  short shouldProcess(in unsigned long   aContentType,
+                      in nsIURI          aContentLocation,
+                      in nsIURI          aRequestOrigin,
+                      in nsISupports     aContext,
                       in ACString        aMimeType,
                       in nsISupports     aExtra);
 
 };
--- a/content/base/src/CSPUtils.jsm
+++ b/content/base/src/CSPUtils.jsm
@@ -103,24 +103,25 @@ this.CSPdebug = function CSPdebug(aMsg) 
 
   aMsg = 'CSP debug: ' + aMsg + "\n";
   Components.classes["@mozilla.org/consoleservice;1"]
                     .getService(Ci.nsIConsoleService)
                     .logStringMessage(aMsg);
 }
 
 // Callback to resume a request once the policy-uri has been fetched
-function CSPPolicyURIListener(policyURI, docRequest, csp) {
+function CSPPolicyURIListener(policyURI, docRequest, csp, reportOnly) {
   this._policyURI = policyURI;    // location of remote policy
   this._docRequest = docRequest;  // the parent document request
   this._csp = csp;                // parent document's CSP
   this._policy = "";              // contents fetched from policyURI
   this._wrapper = null;           // nsIScriptableInputStream
   this._docURI = docRequest.QueryInterface(Ci.nsIChannel)
                  .URI;    // parent document URI (to be used as 'self')
+  this._reportOnly = reportOnly;
 }
 
 CSPPolicyURIListener.prototype = {
 
   QueryInterface: function(iid) {
     if (iid.equals(Ci.nsIStreamListener) ||
         iid.equals(Ci.nsIRequestObserver) ||
         iid.equals(Ci.nsISupports))
@@ -142,18 +143,18 @@ CSPPolicyURIListener.prototype = {
     this._policy += this._wrapper.read(count);
   },
 
   onStopRequest:
   function(request, context, status) {
     if (Components.isSuccessCode(status)) {
       // send the policy we received back to the parent document's CSP
       // for parsing
-      this._csp.refinePolicy(this._policy, this._docURI,
-                             this._csp._specCompliant);
+      this._csp.appendPolicy(this._policy, this._docURI,
+                             this._reportOnly, this._csp._specCompliant);
     }
     else {
       // problem fetching policy so fail closed
       this._csp.refinePolicy("default-src 'none'", this._docURI,
                              this._csp._specCompliant);
     }
     // resume the parent document request
     this._docRequest.resume();
@@ -172,16 +173,17 @@ CSPPolicyURIListener.prototype = {
  */
 this.CSPRep = function CSPRep(aSpecCompliant) {
   // this gets set to true when the policy is done parsing, or when a
   // URI-borne policy has finished loading.
   this._isInitialized = false;
 
   this._allowEval = false;
   this._allowInlineScripts = false;
+  this._reportOnlyMode = false;
 
   // don't auto-populate _directives, so it is easier to find bugs
   this._directives = {};
 
   // Is this a 1.0 spec compliant CSPRep ?
   // Default to false if not specified.
   this._specCompliant = (aSpecCompliant !== undefined) ? aSpecCompliant : false;
 
@@ -239,22 +241,24 @@ CSPRep.ALLOW_DIRECTIVE   = "allow";
   * @param docRequest (optional)
   *        request for the parent document which may need to be suspended
   *        while the policy-uri is asynchronously fetched
   * @param csp (optional)
   *        the CSP object to update once the policy has been fetched
   * @returns
   *        an instance of CSPRep
   */
-CSPRep.fromString = function(aStr, self, docRequest, csp) {
+CSPRep.fromString = function(aStr, self, docRequest, csp, reportOnly) {
+  if (typeof reportOnly === 'undefined') reportOnly = false;
   var SD = CSPRep.SRC_DIRECTIVES_OLD;
   var UD = CSPRep.URI_DIRECTIVES;
   var aCSPR = new CSPRep();
   aCSPR._originalText = aStr;
   aCSPR._innerWindowID = innerWindowFromRequest(docRequest);
+  aCSPR._reportOnlyMode = reportOnly;
 
   var selfUri = null;
   if (self instanceof Ci.nsIURI) {
     selfUri = self.cloneIgnoringRef();
     // clean userpass out of the URI (not used for CSP origin checking, but
     // shows up in prePath).
     try {
       // GetUserPass throws for some protocols without userPass
@@ -446,28 +450,28 @@ CSPRep.fromString = function(aStr, self,
       // suspend the parent document request while we fetch the policy-uri
       try {
         docRequest.suspend();
         var chan = gIoService.newChannel(uri.asciiSpec, null, null);
         // make request anonymous (no cookies, etc.) so the request for the
         // policy-uri can't be abused for CSRF
         chan.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS;
         chan.loadGroup = docRequest.loadGroup;
-        chan.asyncOpen(new CSPPolicyURIListener(uri, docRequest, csp), null);
+        chan.asyncOpen(new CSPPolicyURIListener(uri, docRequest, csp, reportOnly), null);
       }
       catch (e) {
         // resume the document request and apply most restrictive policy
         docRequest.resume();
         cspError(aCSPR, CSPLocalizer.getFormatStr("errorFetchingPolicy",
                                                   [e.toString()]));
         return CSPRep.fromString("default-src 'none'");
       }
 
-      // return a fully-open policy to be intersected with the contents of the
-      // policy-uri when it returns
+      // return a fully-open policy to be used until the contents of the
+      // policy-uri come back.
       return CSPRep.fromString("default-src *");
     }
 
     // UNIDENTIFIED DIRECTIVE /////////////////////////////////////////////
     cspWarn(aCSPR, CSPLocalizer.getFormatStr("couldNotProcessUnknownDirective",
                                              [dirname]));
 
   } // end directive: loop
@@ -494,22 +498,25 @@ CSPRep.fromString = function(aStr, self,
   *        while the policy-uri is asynchronously fetched
   * @param csp (optional)
   *        the CSP object to update once the policy has been fetched
   * @returns
   *        an instance of CSPRep
   */
 // When we deprecate our original CSP implementation, we rename this to
 // CSPRep.fromString and remove the existing CSPRep.fromString above.
-CSPRep.fromStringSpecCompliant = function(aStr, self, docRequest, csp) {
+CSPRep.fromStringSpecCompliant = function(aStr, self, docRequest, csp, reportOnly) {
+  if (typeof reportOnly === 'undefined') reportOnly = false;
+
   var SD = CSPRep.SRC_DIRECTIVES_NEW;
   var UD = CSPRep.URI_DIRECTIVES;
   var aCSPR = new CSPRep(true);
   aCSPR._originalText = aStr;
   aCSPR._innerWindowID = innerWindowFromRequest(docRequest);
+  aCSPR._reportOnlyMode = reportOnly;
 
   var selfUri = null;
   if (self instanceof Ci.nsIURI) {
     selfUri = self.cloneIgnoringRef();
     // clean userpass out of the URI (not used for CSP origin checking, but
     // shows up in prePath).
     try {
       // GetUserPass throws for some protocols without userPass
@@ -711,18 +718,18 @@ CSPRep.fromStringSpecCompliant = functio
       }
       catch (e) {
         // resume the document request and apply most restrictive policy
         docRequest.resume();
         cspError(aCSPR, CSPLocalizer.getFormatStr("errorFetchingPolicy", [e.toString()]));
         return CSPRep.fromStringSpecCompliant("default-src 'none'");
       }
 
-      // return a fully-open policy to be intersected with the contents of the
-      // policy-uri when it returns
+      // return a fully-open policy to be used until the contents of the
+      // policy-uri come back
       return CSPRep.fromStringSpecCompliant("default-src *");
     }
 
     // UNIDENTIFIED DIRECTIVE /////////////////////////////////////////////
     cspWarn(aCSPR, CSPLocalizer.getFormatStr("couldNotProcessUnknownDirective", [dirname]));
 
   } // end directive: loop
 
@@ -832,130 +839,16 @@ CSPRep.prototype = {
     }
 
     // no relevant directives present -- this means for CSP 1.0 that the load
     // should be permitted (and for the old CSP, to block it).
     return this._specCompliant;
   },
 
   /**
-   * Intersects with another CSPRep, deciding the subset policy
-   * that should be enforced, and returning a new instance.
-   * This assumes that either both CSPReps are specCompliant or they are both
-   * not.
-   * @param aCSPRep
-   *        a CSPRep instance to use as "other" CSP
-   * @returns
-   *        a new CSPRep instance of the intersection
-   */
-  intersectWith:
-  function cspsd_intersectWith(aCSPRep) {
-    var newRep = new CSPRep();
-
-    let DIRS = aCSPRep._specCompliant ? CSPRep.SRC_DIRECTIVES_NEW :
-                                        CSPRep.SRC_DIRECTIVES_OLD;
-
-    // one or more of the two CSPReps may not have any given directive.  In
-    // these cases, we need to pick "all" or "none" based on the type of CSPRep
-    // (spec compliant or not).
-    let thisHasDefault = this._directives.hasOwnProperty(DIRS.DEFAULT_SRC),
-        thatHasDefault = aCSPRep._directives.hasOwnProperty(DIRS.DEFAULT_SRC);
-    for (var dir in DIRS) {
-      let dirv = DIRS[dir];
-      let thisHasDir = this._directives.hasOwnProperty(dirv),
-          thatHasDir = aCSPRep._directives.hasOwnProperty(dirv);
-
-
-      // if both specific src directives are absent, skip this (new policy will
-      // rely on default-src)
-      if (!thisHasDir && !thatHasDir) {
-        continue;
-      }
-
-      // frame-ancestors is a special case; it doesn't fall back to
-      // default-src, so only add it to newRep if one or both of the policies
-      // have it.
-      if (dirv === DIRS.FRAME_ANCESTORS) {
-        if (thisHasDir && thatHasDir) {
-          // both have frame-ancestors, intersect them.
-          newRep._directives[dirv] =
-            aCSPRep._directives[dirv].intersectWith(this._directives[dirv]);
-        } else if (thisHasDir || thatHasDir) {
-          // one or the other has frame-ancestors, copy it.
-          newRep._directives[dirv] =
-            ( thisHasDir ? this : aCSPRep )._directives[dirv].clone();
-        }
-      }
-      else if (aCSPRep._specCompliant) {
-        // CSP 1.0 doesn't require default-src, so an intersection only makes
-        // sense if there is a default-src or both policies have the directive.
-
-        if (!thisHasDir && !thisHasDefault) {
-          // only aCSPRep has a relevant directive
-          newRep._directives[dirv] = aCSPRep._directives[dirv].clone();
-        }
-        else if (!thatHasDir && !thatHasDefault) {
-          // only "this" has a relevant directive
-          newRep._directives[dirv] = this._directives[dirv].clone();
-        }
-        else {
-          // both policies have a relevant directive (may be default-src)
-          var isect1 = thisHasDir ?
-                       this._directives[dirv] :
-                       this._directives[DIRS.DEFAULT_SRC];
-          var isect2 = thatHasDir ?
-                       aCSPRep._directives[dirv] :
-                       aCSPRep._directives[DIRS.DEFAULT_SRC];
-          newRep._directives[dirv] = isect1.intersectWith(isect2);
-        }
-      }
-      else {
-        // pre-1.0 CSP requires a default-src, so we can assume it's here
-        // (since the parser created one).
-        var isect1 = thisHasDir ?
-                     this._directives[dirv] :
-                     this._directives[DIRS.DEFAULT_SRC];
-        var isect2 = thatHasDir ?
-                     aCSPRep._directives[dirv] :
-                     aCSPRep._directives[DIRS.DEFAULT_SRC];
-        newRep._directives[dirv] = isect1.intersectWith(isect2);
-      }
-    }
-
-    // REPORT_URI
-    var reportURIDir = CSPRep.URI_DIRECTIVES.REPORT_URI;
-    if (this._directives[reportURIDir] && aCSPRep._directives[reportURIDir]) {
-      newRep._directives[reportURIDir] =
-        this._directives[reportURIDir].concat(aCSPRep._directives[reportURIDir]);
-    }
-    else if (this._directives[reportURIDir]) {
-      // blank concat makes a copy of the string.
-      newRep._directives[reportURIDir] = this._directives[reportURIDir].concat();
-    }
-    else if (aCSPRep._directives[reportURIDir]) {
-      // blank concat makes a copy of the string.
-      newRep._directives[reportURIDir] = aCSPRep._directives[reportURIDir].concat();
-    }
-
-    newRep._allowEval =          this.allowsEvalInScripts
-                           && aCSPRep.allowsEvalInScripts;
-
-    newRep._allowInlineScripts = this.allowsInlineScripts
-                           && aCSPRep.allowsInlineScripts;
-
-    newRep._allowInlineStyles = this.allowsInlineStyles
-                           && aCSPRep.allowsInlineStyles;
-
-    newRep._innerWindowID = this._innerWindowID ?
-                              this._innerWindowID : aCSPRep._innerWindowID;
-
-    return newRep;
-  },
-
-  /**
    * Returns true if "eval" is enabled through the "eval" keyword.
    */
   get allowsEvalInScripts () {
     return this._allowEval;
   },
 
   /**
    * Returns true if inline scripts are enabled through the "inline"
@@ -1187,72 +1080,16 @@ CSPSourceList.prototype = {
     if (this.isAll())     return true;
 
     for (var i in this._sources) {
       if (this._sources[i].permits(aURI)) {
         return true;
       }
     }
     return false;
-  },
-
-  /**
-   * Intersects with another CSPSourceList, deciding the subset directive
-   * that should be enforced, and returning a new instance.
-   * @param that
-   *        the other CSPSourceList to intersect "this" with
-   * @returns
-   *        a new instance of a CSPSourceList representing the intersection
-   */
-  intersectWith:
-  function cspsd_intersectWith(that) {
-
-    var newCSPSrcList = null;
-
-    if (!that) return this.clone();
-
-    if (this.isNone() || that.isNone())
-      newCSPSrcList = CSPSourceList.fromString("'none'", this._CSPRep);
-
-    if (this.isAll()) newCSPSrcList = that.clone();
-    if (that.isAll()) newCSPSrcList = this.clone();
-
-    if (!newCSPSrcList) {
-      // the shortcuts didn't apply, must do intersection the hard way.
-      // --  find only common sources
-
-      // XXX (sid): we should figure out a better algorithm for this.
-      // This is horribly inefficient.
-      var isrcs = [];
-      for (var i in this._sources) {
-        for (var j in that._sources) {
-          var s = that._sources[j].intersectWith(this._sources[i]);
-          if (s) {
-            isrcs.push(s);
-          }
-        }
-      }
-      // Next, remove duplicates
-      dup: for (var i = 0; i < isrcs.length; i++) {
-        for (var j = 0; j < i; j++) {
-          if (isrcs[i].equals(isrcs[j])) {
-            isrcs.splice(i, 1);
-            i--;
-            continue dup;
-          }
-        }
-      }
-      newCSPSrcList = new CSPSourceList();
-      newCSPSrcList._sources = isrcs;
-    }
-
-    if ((!newCSPSrcList._CSPRep) && that._CSPRep) {
-      newCSPSrcList._CSPRep = that._CSPRep;
-    }
-    return newCSPSrcList;
   }
 }
 
 //////////////////////////////////////////////////////////////////////
 /**
  * Class to model a source (scheme, host, port)
  */
 this.CSPSource = function CSPSource() {
@@ -1657,103 +1494,16 @@ CSPSource.prototype = {
     if (this.host && !this.host.permits(aSource.host))
       return false;
 
     // all scheme, host and port matched!
     return true;
   },
 
   /**
-   * Determines the intersection of two sources.
-   * Returns a null object if intersection generates no
-   * hosts that satisfy it.
-   * @param that
-   *        the other CSPSource to intersect "this" with
-   * @returns
-   *        a new instance of a CSPSource representing the intersection
-   */
-  intersectWith:
-  function(that) {
-    var newSource = new CSPSource();
-
-    // 'self' is not part of the intersection.  Intersect the raw values from
-    // the source, self must be set by someone creating this source.
-    // When intersecting, we take the more specific of the two: if one scheme,
-    // host or port is undefined, the other is taken.  (This is contrary to
-    // when "permits" is called -- there, the value of 'self' is looked at
-    // when a scheme, host or port is undefined.)
-
-    // port
-    if (!this.port)
-      newSource._port = that.port;
-    else if (!that.port)
-      newSource._port = this.port;
-    else if (this.port === "*")
-      newSource._port = that.port;
-    else if (that.port === "*")
-      newSource._port = this.port;
-    else if (that.port === this.port)
-      newSource._port = this.port;
-    else {
-      let msg = CSPLocalizer.getFormatStr("notIntersectPort",
-                                          [this.toString(), that.toString()]);
-      cspError(this._CSPRep, msg);
-      return null;
-    }
-
-    // scheme
-    if (!this.scheme)
-      newSource._scheme = that.scheme;
-    else if (!that.scheme)
-      newSource._scheme = this.scheme;
-    if (this.scheme === "*")
-      newSource._scheme = that.scheme;
-    else if (that.scheme === "*")
-      newSource._scheme = this.scheme;
-    else if (that.scheme === this.scheme)
-      newSource._scheme = this.scheme;
-    else {
-      var msg = CSPLocalizer.getFormatStr("notIntersectScheme",
-                                          [this.toString(), that.toString()]);
-      cspError(this._CSPRep, msg);
-      return null;
-    }
-
-    // NOTE: Both sources must have a host, if they don't, something funny is
-    // going on.  The fromString() factory method should have set the host to
-    // * if there's no host specified in the input. Regardless, if a host is
-    // not present either the scheme is hostless or any host should be allowed.
-    // This means we can use the other source's host as the more restrictive
-    // host expression, or if neither are present, we can use "*", but the
-    // error should still be reported.
-
-    // host
-    if (this.host && that.host) {
-      newSource._host = this.host.intersectWith(that.host);
-    } else if (this.host) {
-      let msg = CSPLocalizer.getFormatStr("intersectingSourceWithUndefinedHost",
-                                          [that.toString()]);
-      cspError(this._CSPRep, msg);
-      newSource._host = this.host.clone();
-    } else if (that.host) {
-      let msg = CSPLocalizer.getFormatStr("intersectingSourceWithUndefinedHost",
-                                          [this.toString()]);
-      cspError(this._CSPRep, msg);
-      newSource._host = that.host.clone();
-    } else {
-      let msg = CSPLocalizer.getFormatStr("intersectingSourcesWithUndefinedHosts",
-                                          [this.toString(), that.toString()]);
-      cspError(this._CSPRep, msg);
-      newSource._host = CSPHost.fromString("*");
-    }
-
-    return newSource;
-  },
-
-  /**
    * Compares one CSPSource to another.
    *
    * @param that
    *        another CSPSource
    * @param resolveSelf (optional)
    *        if present, and 'true', implied values are obtained from 'self'
    *        instead of assumed to be "anything"
    * @returns
@@ -1892,46 +1642,16 @@ CSPHost.prototype = {
       }
     }
 
     // at this point, all conditions are met, so the host is allowed
     return true;
   },
 
   /**
-   * Determines the intersection of two Hosts.
-   * Basically, they must be the same, or one must have a wildcard.
-   * @param that
-   *        the other CSPHost to intersect "this" with
-   * @returns
-   *        a new instance of a CSPHost representing the intersection
-   *        (or null, if they can't be intersected)
-   */
-  intersectWith:
-  function(that) {
-    if (!(this.permits(that) || that.permits(this))) {
-      // host definitions cannot co-exist without a more general host
-      // ... one must be a subset of the other, or intersection makes no sense.
-      return null;
-    }
-
-    // pick the more specific one, if both are same length.
-    if (this._segments.length == that._segments.length) {
-      // *.a vs b.a : b.a
-      return (this._segments[0] === "*") ? that.clone() : this.clone();
-    }
-
-    // different lengths...
-    // *.b.a vs *.a : *.b.a
-    // *.b.a vs d.c.b.a : d.c.b.a
-    return (this._segments.length > that._segments.length) ?
-            this.clone() : that.clone();
-  },
-
-  /**
    * Compares one CSPHost to another.
    *
    * @param that
    *        another CSPHost
    * @returns
    *        true if they have the same data
    */
   equals:
--- a/content/base/src/contentSecurityPolicy.js
+++ b/content/base/src/contentSecurityPolicy.js
@@ -23,43 +23,41 @@ const CSP_VIOLATION_TOPIC = "csp-on-viol
 // Needed to support CSP 1.0 spec and our original CSP implementation - should
 // be removed when our original implementation is deprecated.
 const CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT = "csp_type_xmlhttprequest_spec_compliant";
 const CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT = "csp_type_websocket_spec_compliant";
 
 const WARN_FLAG = Ci.nsIScriptError.warningFlag;
 const ERROR_FLAG = Ci.nsIScriptError.ERROR_FLAG;
 
+const INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Inline Stylesheets will not apply';
+const INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Inline Scripts will not execute';
+const EVAL_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Code will not be created from strings';
+
 // The cutoff length of content location in creating CSP cache key.
 const CSP_CACHE_URI_CUTOFF_SIZE = 512;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/CSPUtils.jsm");
 
 /* ::::: Policy Parsing & Data structures :::::: */
 
 function ContentSecurityPolicy() {
   CSPdebug("CSP CREATED");
   this._isInitialized = false;
-  this._reportOnlyMode = false;
 
-  this._policy = CSPRep.fromString("default-src *");
-
-  // default options "wide open" since this policy will be intersected soon
-  this._policy._allowInlineScripts = true;
-  this._policy._allowInlineStyles = true;
-  this._policy._allowEval = true;
+  this._policies = [];
 
   this._request = "";
   this._requestOrigin = "";
   this._requestPrincipal = "";
   this._referrer = "";
   this._docRequest = null;
-  CSPdebug("CSP POLICY INITED TO 'default-src *'");
+  CSPdebug("CSP object initialized, no policies to enforce yet");
 
   this._cache = { };
 }
 
 /*
  * Set up mappings from nsIContentPolicy content types to CSP directives.
  */
 {
@@ -126,100 +124,127 @@ ContentSecurityPolicy.prototype = {
   get isInitialized() {
     return this._isInitialized;
   },
 
   set isInitialized (foo) {
     this._isInitialized = foo;
   },
 
-  get policy () {
-    return this._policy.toString();
-  },
-
-  getAllowsInlineScript: function(shouldReportViolation) {
-    // report it?
-    shouldReportViolation.value = !this._policy.allowsInlineScripts;
-
-    // allow it to execute?
-    return this._reportOnlyMode || this._policy.allowsInlineScripts;
+  _getPolicyInternal: function(index) {
+    if (index < 0 || index >= this._policies.length) {
+      throw Cr.NS_ERROR_FAILURE;
+    }
+    return this._policies[index];
   },
 
-  getAllowsEval: function(shouldReportViolation) {
-    // report it?
-    shouldReportViolation.value = !this._policy.allowsEvalInScripts;
-
-    // allow it to execute?
-    return this._reportOnlyMode || this._policy.allowsEvalInScripts;
+  _buildViolatedDirectiveString:
+  function(aDirectiveName, aPolicy) {
+    var SD = CSPRep.SRC_DIRECTIVES_NEW;
+    var cspContext = (SD[aDirectiveName] in aPolicy._directives) ? SD[aDirectiveName] : SD.DEFAULT_SRC;
+    var directive = aPolicy._directives[cspContext];
+    return cspContext + ' ' + directive.toString();
   },
 
-  getAllowsInlineStyle: function(shouldReportViolation) {
-    // report it?
-    shouldReportViolation.value = !this._policy.allowsInlineStyles;
-
-    // allow it to execute?
-    return this._reportOnlyMode || this._policy.allowsInlineStyles;
+  /**
+   * Returns policy string representing the policy at "index".
+   */
+  getPolicy: function(index) {
+    return this._getPolicyInternal(index).toString();
   },
 
   /**
-   * Log policy violation on the Error Console and send a report if a report-uri
-   * is present in the policy
+   * Returns count of policies.
+   */
+  get numPolicies() {
+    return this._policies.length;
+  },
+
+  getAllowsInlineScript: function(shouldReportViolations) {
+    // report it? (for each policy, is it violated?)
+    shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsInlineScripts; });
+
+    // allow it to execute?  (Do all the policies allow it to execute)?
+    return this._policies.every(function(a) {
+      return a._reportOnlyMode || a.allowsInlineScripts;
+    });
+  },
+
+  getAllowsEval: function(shouldReportViolations) {
+    // report it? (for each policy, is it violated?)
+    shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsEvalInScripts; });
+
+    // allow it to execute?  (Do all the policies allow it to execute)?
+    return this._policies.every(function(a) {
+      return a._reportOnlyMode || a.allowsEvalInScripts;
+    });
+  },
+
+  getAllowsInlineStyle: function(shouldReportViolations) {
+    // report it? (for each policy, is it violated?)
+    shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsInlineStyles; });
+
+    // allow it to execute?  (Do all the policies allow it to execute)?
+    return this._policies.every(function(a) {
+      return a._reportOnlyMode || a.allowsInlineStyles;
+    });
+  },
+
+  /**
+   * For each policy, log any violation on the Error Console and send a report
+   * if a report-uri is present in the policy
    *
    * @param aViolationType
    *     one of the VIOLATION_TYPE_* constants, e.g. inline-script or eval
    * @param aSourceFile
    *     name of the source file containing the violation (if available)
    * @param aContentSample
    *     sample of the violating content (to aid debugging)
    * @param aLineNum
    *     source line number of the violation (if available)
    */
   logViolationDetails:
-  function(aViolationType, aSourceFile, aScriptSample, aLineNum) {
-    // allowsInlineScript and allowsEval both return true when report-only mode
-    // is enabled, resulting in a call to this function. Therefore we need to
-    // check that the policy was in fact violated before logging any violations
-    switch (aViolationType) {
-    case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_STYLE:
-      if (!this._policy.allowsInlineStyles)
-        this._asyncReportViolation('self', null, CSPLocalizer.getStr("inlineStyleBlocked"),
-                                   'violated base restriction: Inline Stylesheets will not apply',
-                                   aSourceFile, aScriptSample, aLineNum);
-      break;
-    case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT:
-      if (!this._policy.allowsInlineScripts)
-        this._asyncReportViolation('self', null, CSPLocalizer.getStr("inlineScriptBlocked"),
-                                   'violated base restriction: Inline Scripts will not execute',
-                                   aSourceFile, aScriptSample, aLineNum);
-      break;
-    case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_EVAL:
-      if (!this._policy.allowsEvalInScripts)
-        this._asyncReportViolation('self', null, CSPLocalizer.getStr("scriptFromStringBlocked"),
-                                   'violated base restriction: Code will not be created from strings',
-                                   aSourceFile, aScriptSample, aLineNum);
-      break;
+  function(aViolationType, aSourceFile, aScriptSample, aLineNum, violatedPolicyIndex) {
+    for (let policyIndex=0; policyIndex < this._policies.length; policyIndex++) {
+      let policy = this._policies[policyIndex];
+
+      // call-sites to the eval/inline checks recieve two return values: allows
+      // and violates.  Policies that are report-only allow the
+      // loads/compilations but violations should still be reported.  Not all
+      // policies in this nsIContentSecurityPolicy instance will be violated,
+      // which is why we must check again here.
+      switch (aViolationType) {
+      case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_STYLE:
+        if (!policy.allowsInlineStyles) {
+          var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy);
+          this._asyncReportViolation('self', null, violatedDirective, policyIndex,
+                                    INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT,
+                                    aSourceFile, aScriptSample, aLineNum);
+        }
+        break;
+      case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT:
+        if (!policy.allowsInlineScripts)    {
+          var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
+          this._asyncReportViolation('self', null, violatedDirective, policyIndex,
+                                    INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT,
+                                    aSourceFile, aScriptSample, aLineNum);
+          }
+        break;
+      case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_EVAL:
+        if (!policy.allowsEvalInScripts) {
+          var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
+          this._asyncReportViolation('self', null, violatedDirective, policyIndex,
+                                    EVAL_VIOLATION_OBSERVER_SUBJECT,
+                                    aSourceFile, aScriptSample, aLineNum);
+        }
+        break;
+      }
     }
   },
 
-  set reportOnlyMode(val) {
-    this._reportOnlyMode = val;
-  },
-
-  get reportOnlyMode () {
-    return this._reportOnlyMode;
-  },
-
-  /*
-  // Having a setter is a bad idea... opens up the policy to "loosening"
-  // Instead, use "refinePolicy."
-  set policy (aStr) {
-    this._policy = CSPRep.fromString(aStr);
-  },
-  */
-
   /**
    * Given an nsIHttpChannel, fill out the appropriate data.
    */
   scanRequestData:
   function(aChannel) {
     if (!aChannel)
       return;
 
@@ -245,86 +270,109 @@ ContentSecurityPolicy.prototype = {
       } catch (ex) {}
       this._referrer = referrer.asciiSpec;
     }
   },
 
 /* ........ Methods .............. */
 
   /**
-   * Given a new policy, intersects the currently enforced policy with the new
-   * one and stores the result.  The effect is a "tightening" or refinement of
-   * an old policy.  This is called any time a new policy is encountered and
-   * the effective policy has to be refined.
+   * Adds a new policy to our list of policies for this CSP context.
+   * @returns the count of policies.
    */
-  refinePolicy:
-  function csp_refinePolicy(aPolicy, selfURI, aSpecCompliant) {
-    CSPdebug("REFINE POLICY: " + aPolicy);
-    CSPdebug("         SELF: " + selfURI.asciiSpec);
+  appendPolicy:
+  function csp_appendPolicy(aPolicy, selfURI, aReportOnly, aSpecCompliant) {
+#ifndef MOZ_B2G
+    CSPdebug("APPENDING POLICY: " + aPolicy);
+    CSPdebug("            SELF: " + selfURI.asciiSpec);
     CSPdebug("CSP 1.0 COMPLIANT : " + aSpecCompliant);
+#endif
+
     // For nested schemes such as view-source: make sure we are taking the
     // innermost URI to use as 'self' since that's where we will extract the
     // scheme, host and port from
     if (selfURI instanceof Ci.nsINestedURI) {
+#ifndef MOZ_B2G
       CSPdebug("        INNER: " + selfURI.innermostURI.asciiSpec);
+#endif
       selfURI = selfURI.innermostURI;
     }
 
-    // stay uninitialized until policy merging is done
-    this._isInitialized = false;
+    // stay uninitialized until policy setup is done
+    var newpolicy;
 
     // If there is a policy-uri, fetch the policy, then re-call this function.
     // (1) parse and create a CSPRep object
     // Note that we pass the full URI since when it's parsed as 'self' to construct a
     // CSPSource only the scheme, host, and port are kept.
 
     // If we want to be CSP 1.0 spec compliant, use the new parser.
     // The old one will be deprecated in the future and will be
     // removed at that time.
-    var newpolicy;
     if (aSpecCompliant) {
       newpolicy = CSPRep.fromStringSpecCompliant(aPolicy,
                                                  selfURI,
                                                  this._docRequest,
-                                                 this);
+                                                 this,
+                                                 aReportOnly);
     } else {
       newpolicy = CSPRep.fromString(aPolicy,
                                     selfURI,
                                     this._docRequest,
-                                    this);
+                                    this,
+                                    aReportOnly);
     }
 
-    // (2) Intersect the currently installed CSPRep object with the new one
-    var intersect = this._policy.intersectWith(newpolicy);
+    newpolicy._specCompliant = !!aSpecCompliant;
+    newpolicy._isInitialized = true;
+    this._policies.push(newpolicy);
+    this._cache = {}; // reset cache since effective policy changes
+  },
 
-    // (3) Save the result
-    this._policy = intersect;
-
-    this._policy._specCompliant = !!aSpecCompliant;
-
-    this._isInitialized = true;
-    this._cache = {};
+  /**
+   * Removes a policy from the array of policies.
+   */
+  removePolicy:
+  function csp_removePolicy(index) {
+    if (index < 0 || index >= this._policies.length) {
+      CSPdebug("Cannot remove policy " + index + "; not enough policies.");
+      return;
+    }
+    this._policies.splice(index, 1);
+    this._cache = {}; // reset cache since effective policy changes
   },
 
   /**
    * Generates and sends a violation report to the specified report URIs.
    */
   sendReports:
   function(blockedUri, originalUri, violatedDirective,
-           aSourceFile, aScriptSample, aLineNum) {
-    var uriString = this._policy.getReportURIs();
+           violatedPolicyIndex, aSourceFile,
+           aScriptSample, aLineNum) {
+
+    let policy = this._getPolicyInternal(violatedPolicyIndex);
+    if (!policy) {
+      CSPdebug("ERROR in SendReports: policy " + violatedPolicyIndex + " is not defined.");
+      return;
+    }
+
+    var uriString = policy.getReportURIs();
     var uris = uriString.split(/\s+/);
     if (uris.length > 0) {
       // see if we need to sanitize the blocked-uri
       let blocked = '';
       if (originalUri) {
         // We've redirected, only report the blocked origin
-        let clone = blockedUri.clone();
-        clone.path = '';
-        blocked = clone.asciiSpec;
+        try {
+          let clone = blockedUri.clone();
+          clone.path = '';
+          blocked = clone.asciiSpec;
+        } catch(e) {
+          CSPdebug(".... blockedUri can't be cloned: " + blockedUri);
+        }
       }
       else if (blockedUri instanceof Ci.nsIURI) {
         blocked = blockedUri.cloneIgnoringRef().asciiSpec;
       }
       else {
         // blockedUri is a string for eval/inline-script violations
         blocked = blockedUri;
       }
@@ -355,27 +403,16 @@ ContentSecurityPolicy.prototype = {
       if (aScriptSample)
         report["csp-report"]["script-sample"] = aScriptSample;
       if (aLineNum)
         report["csp-report"]["line-number"] = aLineNum;
 
       var reportString = JSON.stringify(report);
       CSPdebug("Constructed violation report:\n" + reportString);
 
-      var violationMessage = null;
-      if (blockedUri["asciiSpec"]) {
-         violationMessage = CSPLocalizer.getFormatStr("CSPViolationWithURI", [violatedDirective, blockedUri.asciiSpec]);
-      } else {
-         violationMessage = CSPLocalizer.getFormatStr("CSPViolation", [violatedDirective]);
-      }
-      this._policy.log(WARN_FLAG, violationMessage,
-                        (aSourceFile) ? aSourceFile : null,
-                        (aScriptSample) ? decodeURIComponent(aScriptSample) : null,
-                        (aLineNum) ? aLineNum : null);
-
       // For each URI in the report list, send out a report.
       // We make the assumption that all of the URIs are absolute URIs; this
       // should be taken care of in CSPRep.fromString (where it converts any
       // relative URIs into absolute ones based on "self").
       for (let i in uris) {
         if (uris[i] === "")
           continue;
 
@@ -391,17 +428,17 @@ ContentSecurityPolicy.prototype = {
           content.data = reportString + "\n\n";
 
           // make sure this is an anonymous request (no cookies) so in case the
           // policy URI is injected, it can't be abused for CSRF.
           chan.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS;
 
           // we need to set an nsIChannelEventSink on the channel object
           // so we can tell it to not follow redirects when posting the reports
-          chan.notificationCallbacks = new CSPReportRedirectSink(this._policy);
+          chan.notificationCallbacks = new CSPReportRedirectSink(policy);
           if (this._docRequest) {
             chan.loadGroup = this._docRequest.loadGroup;
           }
 
           chan.QueryInterface(Ci.nsIUploadChannel)
               .setUploadStream(content, "application/json", content.available());
 
           try {
@@ -426,35 +463,78 @@ ContentSecurityPolicy.prototype = {
           }
 
           //send data (and set up error notifications)
           chan.asyncOpen(new CSPViolationReportListener(uris[i]), null);
           CSPdebug("Sent violation report to " + uris[i]);
         } catch(e) {
           // it's possible that the URI was invalid, just log a
           // warning and skip over that.
-          this._policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("triedToSendReport", [uris[i]]));
-          this._policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("errorWas", [e.toString()]));
+          policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("triedToSendReport", [uris[i]]));
+          policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("errorWas", [e.toString()]));
         }
       }
     }
   },
 
   /**
+   * Logs a meaningful CSP warning to the developer console.
+   */
+  logToConsole:
+  function(blockedUri, originalUri, violatedDirective, aViolatedPolicyIndex,
+           aSourceFile, aScriptSample, aLineNum, aObserverSubject) {
+     let policy = this._policies[aViolatedPolicyIndex];
+     switch(aObserverSubject.data) {
+      case INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT:
+        violatedDirective = CSPLocalizer.getStr("inlineStyleBlocked");
+        break;
+      case INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT:
+        violatedDirective = CSPLocalizer.getStr("inlineScriptBlocked");
+        break;
+      case EVAL_VIOLATION_OBSERVER_SUBJECT:
+        violatedDirective = CSPLocalizer.getStr("scriptFromStringBlocked");
+        break;
+    }
+    var violationMessage = null;
+    if (blockedUri["asciiSpec"]) {
+      violationMessage = CSPLocalizer.getFormatStr("CSPViolationWithURI", [violatedDirective, blockedUri.asciiSpec]);
+    } else {
+      violationMessage = CSPLocalizer.getFormatStr("CSPViolation", [violatedDirective]);
+    }
+    policy.log(WARN_FLAG, violationMessage,
+               (aSourceFile) ? aSourceFile : null,
+               (aScriptSample) ? decodeURIComponent(aScriptSample) : null,
+               (aLineNum) ? aLineNum : null);
+  },
+
+/**
    * Exposed Method to analyze docShell for approved frame ancestry.
-   * Also sends violation reports if necessary.
+   * NOTE: Also sends violation reports if necessary.
    * @param docShell
    *    the docShell for this policy's resource.
    * @return
-   *    true if the frame ancestry is allowed by this policy.
+   *    true if the frame ancestry is allowed by this policy and the load
+   *    should progress.
    */
   permitsAncestry:
   function(docShell) {
+    // Cannot shortcut checking all the policies since violation reports have
+    // to be triggered if any policy wants it.
+    var permitted = true;
+    for (let i = 0; i < this._policies.length; i++) {
+      if (!this._permitsAncestryInternal(docShell, this._policies[i], i)) {
+        permitted = false;
+      }
+    }
+    return permitted;
+  },
+
+  _permitsAncestryInternal:
+  function(docShell, policy, policyIndex) {
     if (!docShell) { return false; }
-    CSPdebug(" in permitsAncestry(), docShell = " + docShell);
 
     // walk up this docShell tree until we hit chrome
     var dst = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                       .getInterface(Ci.nsIDocShellTreeItem);
 
     // collect ancestors and make sure they're allowed.
     var ancestors = [];
     while (dst.parent) {
@@ -466,38 +546,39 @@ ContentSecurityPolicy.prototype = {
           break;
         }
         // delete any userpass
         let ancestor = it.currentURI.cloneIgnoringRef();
         try { // GetUserPass throws for some protocols without userPass
           ancestor.userPass = '';
         } catch (ex) {}
 
+#ifndef MOZ_B2G
         CSPdebug(" found frame ancestor " + ancestor.asciiSpec);
+#endif
         ancestors.push(ancestor);
       }
     }
 
     // scan the discovered ancestors
     // frame-ancestors is the same in both old and new source directives,
     // so don't need to differentiate here.
     let cspContext = CSPRep.SRC_DIRECTIVES_NEW.FRAME_ANCESTORS;
     for (let i in ancestors) {
       let ancestor = ancestors[i];
-      if (!this._policy.permits(ancestor, cspContext)) {
+      if (!policy.permits(ancestor, cspContext)) {
         // report the frame-ancestor violation
-        let directive = this._policy._directives[cspContext];
-        let violatedPolicy = (directive._isImplicit
-                                ? 'default-src' : 'frame-ancestors ')
-                                + directive.toString();
+        let directive = policy._directives[cspContext];
+        let violatedPolicy = 'frame-ancestors ' + directive.toString();
 
-        this._asyncReportViolation(ancestors[i], null, violatedPolicy);
+        this._asyncReportViolation(ancestors[i], null, violatedPolicy,
+                                   policyIndex);
 
         // need to lie if we are testing in report-only mode
-        return this._reportOnlyMode;
+        return policy._reportOnlyMode;
       }
     }
     return true;
   },
 
   /**
    * Creates a cache key from content location and content type.
    */
@@ -543,73 +624,92 @@ ContentSecurityPolicy.prototype = {
     var cspContext = ContentSecurityPolicy._MAPPINGS[aContentType];
 
     // The mapping for XHR and websockets is different between our original
     // implementation and the 1.0 spec, we handle this here.
     var cspContext;
 
     let cp = Ci.nsIContentPolicy;
 
+    // iterate through all the _policies and send reports where a policy is
+    // violated.  After the check, determine the overall effect (blocked or
+    // loaded?) and cache it.
+    let policyAllowsLoadArray = [];
+    for (let policyIndex=0; policyIndex < this._policies.length; policyIndex++) {
+      let policy = this._policies[policyIndex];
+
 #ifndef MOZ_B2G
-    CSPdebug("policy is " + (this._policy._specCompliant ?
-                             "1.0 compliant" : "pre-1.0"));
+      CSPdebug("policy is " + (policy._specCompliant ?
+                              "1.0 compliant" : "pre-1.0"));
+      CSPdebug("policy is " + (policy._reportOnlyMode ?
+                              "report-only" : "blocking"));
+#endif
+
+      if (aContentType == cp.TYPE_XMLHTTPREQUEST && this._policies[policyIndex]._specCompliant) {
+        cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT];
+      } else if (aContentType == cp.TYPE_WEBSOCKET && this._policies[policyIndex]._specCompliant) {
+        cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT];
+      } else {
+        cspContext = ContentSecurityPolicy._MAPPINGS[aContentType];
+      }
+
+#ifndef MOZ_B2G
+      CSPdebug("shouldLoad cspContext = " + cspContext);
 #endif
 
-    if (aContentType == cp.TYPE_XMLHTTPREQUEST && this._policy._specCompliant) {
-      cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT];
-    } else if (aContentType == cp.TYPE_WEBSOCKET && this._policy._specCompliant) {
-      cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT];
-    } else {
-      cspContext = ContentSecurityPolicy._MAPPINGS[aContentType];
-    }
-
-    CSPdebug("shouldLoad cspContext = " + cspContext);
+      // if the mapping is null, there's no policy, let it through.
+      if (!cspContext) {
+        return Ci.nsIContentPolicy.ACCEPT;
+      }
 
-    // if the mapping is null, there's no policy, let it through.
-    if (!cspContext) {
-      return Ci.nsIContentPolicy.ACCEPT;
-    }
+      // otherwise, honor the translation
+      // var source = aContentLocation.scheme + "://" + aContentLocation.hostPort;
+      var res = policy.permits(aContentLocation, cspContext)
+                ? cp.ACCEPT : cp.REJECT_SERVER;
+      // record whether the thing should be blocked or just reported.
+      policyAllowsLoadArray.push(res == cp.ACCEPT || policy._reportOnlyMode);
 
-    // otherwise, honor the translation
-    // var source = aContentLocation.scheme + "://" + aContentLocation.hostPort;
-    var res = this._policy.permits(aContentLocation, cspContext)
-              ? Ci.nsIContentPolicy.ACCEPT
-              : Ci.nsIContentPolicy.REJECT_SERVER;
+      // frame-ancestors is taken care of early on (as this document is loaded)
 
-    // frame-ancestors is taken care of early on (as this document is loaded)
+      // If the result is *NOT* ACCEPT, then send report
+      if (res != Ci.nsIContentPolicy.ACCEPT) {
+        CSPdebug("blocking request for " + aContentLocation.asciiSpec);
+        try {
+          let directive = "unknown directive",
+              violatedPolicy = "unknown policy";
 
-    // If the result is *NOT* ACCEPT, then send report
-    if (res != Ci.nsIContentPolicy.ACCEPT) {
-      CSPdebug("blocking request for " + aContentLocation.asciiSpec);
-      try {
-        let directive = "unknown directive",
-            violatedPolicy = "unknown policy";
+          // The policy might not explicitly declare each source directive (so
+          // the cspContext may be implicit).  If so, we have to report
+          // violations as appropriate: specific or the default-src directive.
+          if (policy._directives.hasOwnProperty(cspContext)) {
+            directive = policy._directives[cspContext];
+            violatedPolicy = cspContext + ' ' + directive.toString();
+          } else if (policy._directives.hasOwnProperty("default-src")) {
+            directive = policy._directives["default-src"];
+            violatedPolicy = "default-src " + directive.toString();
+          } else {
+            violatedPolicy = "unknown directive";
+            CSPdebug('ERROR in blocking content: ' +
+                    'CSP is not sure which part of the policy caused this block');
+          }
 
-        // The policy might not explicitly declare each source directive (so
-        // the cspContext may be implicit).  If so, we have to report
-        // violations as appropriate: specific or the default-src directive.
-        if (this._policy._directives.hasOwnProperty(cspContext)) {
-          directive = this._policy._directives[cspContext];
-          violatedPolicy = cspContext + ' ' + directive.toString();
-        } else if (this._policy._directives.hasOwnProperty("default-src")) {
-          directive = this._policy._directives["default-src"];
-          violatedPolicy = "default-src " + directive.toString();
-        } else {
-          violatedPolicy = "unknown directive";
-          CSPdebug('ERROR in blocking content: ' +
-                   'CSP is not sure which part of the policy caused this block');
+          this._asyncReportViolation(aContentLocation, aOriginalUri,
+                                     violatedPolicy, policyIndex);
+        } catch(e) {
+          CSPdebug('---------------- ERROR: ' + e);
         }
+      }
+    } // end for-each loop over policies
 
-        this._asyncReportViolation(aContentLocation, aOriginalUri, violatedPolicy);
-      } catch(e) {
-        CSPdebug('---------------- ERROR: ' + e);
-      }
-    }
+    // the ultimate decision is based on whether any policies want to reject
+    // the load.  The array keeps track of whether the policies allowed the
+    // loads. If any doesn't, we'll reject the load (and cache the result).
+    let ret = (policyAllowsLoadArray.some(function(a,b) { return !a; }) ?
+               cp.REJECT_SERVER : cp.ACCEPT);
 
-    let ret = (this._reportOnlyMode ? Ci.nsIContentPolicy.ACCEPT : res);
     if (key) {
       this._cache[key] = ret;
     }
     return ret;
   },
 
   shouldProcess:
   function csp_shouldProcess(aContentType,
@@ -624,60 +724,69 @@ ContentSecurityPolicy.prototype = {
     return res;
   },
 
   /**
    * Asynchronously notifies any nsIObservers listening to the CSP violation
    * topic that a violation occurred.  Also triggers report sending.  All
    * asynchronous on the main thread.
    *
-   * @param blockedContentSource
+   * @param aBlockedContentSource
    *        Either a CSP Source (like 'self', as string) or nsIURI: the source
    *        of the violation.
-   * @param originalUri
+   * @param aOriginalUri
    *        The original URI if the blocked content is a redirect, else null
-   * @param violatedDirective
+   * @param aViolatedDirective
    *        the directive that was violated (string).
-   * @param observerSubject
+   * @param aViolatedPolicyIndex
+   *        the index of the policy that was violated (so we know where to send
+   *        the reports).
+   * @param aObserverSubject
    *        optional, subject sent to the nsIObservers listening to the CSP
    *        violation topic.
    * @param aSourceFile
    *        name of the file containing the inline script violation
    * @param aScriptSample
    *        a sample of the violating inline script
    * @param aLineNum
    *        source line number of the violation (if available)
    */
   _asyncReportViolation:
-  function(blockedContentSource, originalUri, violatedDirective, observerSubject,
+  function(aBlockedContentSource, aOriginalUri, aViolatedDirective,
+           aViolatedPolicyIndex, aObserverSubject,
            aSourceFile, aScriptSample, aLineNum) {
     // if optional observerSubject isn't specified, default to the source of
     // the violation.
-    if (!observerSubject)
-      observerSubject = blockedContentSource;
+    if (!aObserverSubject)
+      aObserverSubject = aBlockedContentSource;
 
     // gotta wrap things that aren't nsISupports, since it's sent out to
     // observers as such.  Objects that are not nsISupports are converted to
     // strings and then wrapped into a nsISupportsCString.
-    if (!(observerSubject instanceof Ci.nsISupports)) {
-      let d = observerSubject;
-      observerSubject = Cc["@mozilla.org/supports-cstring;1"]
+    if (!(aObserverSubject instanceof Ci.nsISupports)) {
+      let d = aObserverSubject;
+      aObserverSubject = Cc["@mozilla.org/supports-cstring;1"]
                           .createInstance(Ci.nsISupportsCString);
-      observerSubject.data = d;
+      aObserverSubject.data = d;
     }
 
     var reportSender = this;
     Services.tm.mainThread.dispatch(
       function() {
-        Services.obs.notifyObservers(observerSubject,
+        Services.obs.notifyObservers(aObserverSubject,
                                      CSP_VIOLATION_TOPIC,
-                                     violatedDirective);
-        reportSender.sendReports(blockedContentSource, originalUri,
-                                 violatedDirective,
+                                     aViolatedDirective);
+        reportSender.sendReports(aBlockedContentSource, aOriginalUri,
+                                 aViolatedDirective, aViolatedPolicyIndex,
                                  aSourceFile, aScriptSample, aLineNum);
+        reportSender.logToConsole(aBlockedContentSource, aOriginalUri,
+                                  aViolatedDirective, aViolatedPolicyIndex,
+                                  aSourceFile, aScriptSample,
+                                  aLineNum, aObserverSubject);
+
       }, Ci.nsIThread.DISPATCH_NORMAL);
   },
 };
 
 // The POST of the violation report (if it happens) should not follow
 // redirects, per the spec. hence, we implement an nsIChannelEventSink
 // with an object so we can tell XHR to abort if a redirect happens.
 function CSPReportRedirectSink(policy) {
--- a/content/base/src/nsCSPService.cpp
+++ b/content/base/src/nsCSPService.cpp
@@ -55,156 +55,173 @@ CSPService::ShouldLoad(uint32_t aContent
                        nsIURI *aContentLocation,
                        nsIURI *aRequestOrigin,
                        nsISupports *aRequestContext,
                        const nsACString &aMimeTypeGuess,
                        nsISupports *aExtra,
                        nsIPrincipal *aRequestPrincipal,
                        int16_t *aDecision)
 {
-    if (!aContentLocation)
-        return NS_ERROR_FAILURE;
+  if (!aContentLocation)
+    return NS_ERROR_FAILURE;
 
 #ifdef PR_LOGGING
-    {
-        nsAutoCString location;
-        aContentLocation->GetSpec(location);
-        PR_LOG(gCspPRLog, PR_LOG_DEBUG,
-            ("CSPService::ShouldLoad called for %s", location.get()));
-    }
+  {
+    nsAutoCString location;
+    aContentLocation->GetSpec(location);
+    PR_LOG(gCspPRLog, PR_LOG_DEBUG,
+           ("CSPService::ShouldLoad called for %s", location.get()));
+  }
 #endif
-    // default decision, CSP can revise it if there's a policy to enforce
-    *aDecision = nsIContentPolicy::ACCEPT;
+
+  // default decision, CSP can revise it if there's a policy to enforce
+  *aDecision = nsIContentPolicy::ACCEPT;
 
-    // No need to continue processing if CSP is disabled
-    if (!sCSPEnabled)
-        return NS_OK;
+  // No need to continue processing if CSP is disabled
+  if (!sCSPEnabled)
+    return NS_OK;
 
-    // shortcut for about: chrome: and resource: and javascript: uris since
-    // they're not subject to CSP content policy checks.
-    bool schemeMatch = false;
-    NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("about", &schemeMatch), NS_OK);
-    if (schemeMatch)
-        return NS_OK;
-    NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("chrome", &schemeMatch), NS_OK);
-    if (schemeMatch)
-        return NS_OK;
-    NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("resource", &schemeMatch), NS_OK);
-    if (schemeMatch)
-        return NS_OK;
-    NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("javascript", &schemeMatch), NS_OK);
-    if (schemeMatch)
-        return NS_OK;
+  // shortcut for about: chrome: and resource: and javascript: uris since
+  // they're not subject to CSP content policy checks.
+  bool schemeMatch = false;
+  NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("about", &schemeMatch), NS_OK);
+  if (schemeMatch)
+    return NS_OK;
+  NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("chrome", &schemeMatch), NS_OK);
+  if (schemeMatch)
+    return NS_OK;
+  NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("resource", &schemeMatch), NS_OK);
+  if (schemeMatch)
+    return NS_OK;
+  NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("javascript", &schemeMatch), NS_OK);
+  if (schemeMatch)
+    return NS_OK;
 
 
-    // These content types are not subject to CSP content policy checks:
-    // TYPE_CSP_REPORT, TYPE_REFRESH, TYPE_DOCUMENT
-    // (their mappings are null in contentSecurityPolicy.js)
-    if (aContentType == nsIContentPolicy::TYPE_CSP_REPORT ||
-        aContentType == nsIContentPolicy::TYPE_REFRESH ||
-        aContentType == nsIContentPolicy::TYPE_DOCUMENT) {
-        return NS_OK;
-    }
+  // These content types are not subject to CSP content policy checks:
+  // TYPE_CSP_REPORT, TYPE_REFRESH, TYPE_DOCUMENT
+  // (their mappings are null in contentSecurityPolicy.js)
+  if (aContentType == nsIContentPolicy::TYPE_CSP_REPORT ||
+    aContentType == nsIContentPolicy::TYPE_REFRESH ||
+    aContentType == nsIContentPolicy::TYPE_DOCUMENT) {
+    return NS_OK;
+  }
 
-    // find the principal of the document that initiated this request and see
-    // if it has a CSP policy object
-    nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext));
-    nsCOMPtr<nsIPrincipal> principal;
-    nsCOMPtr<nsIContentSecurityPolicy> csp;
-    if (node) {
-        principal = node->NodePrincipal();
-        principal->GetCsp(getter_AddRefs(csp));
+  // find the principal of the document that initiated this request and see
+  // if it has a CSP policy object
+  nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext));
+  nsCOMPtr<nsIPrincipal> principal;
+  nsCOMPtr<nsIContentSecurityPolicy> csp;
+  if (node) {
+    principal = node->NodePrincipal();
+    principal->GetCsp(getter_AddRefs(csp));
 
-        if (csp) {
+    if (csp) {
 #ifdef PR_LOGGING
+      {
+        int numPolicies = 0;
+        nsresult rv = csp->GetPolicyCount(&numPolicies);
+        if (NS_SUCCEEDED(rv)) {
+          for (int i=0; i<numPolicies; i++) {
             nsAutoString policy;
-            csp->GetPolicy(policy);
+            csp->GetPolicy(i, policy);
             PR_LOG(gCspPRLog, PR_LOG_DEBUG,
-                    ("Document has CSP: %s",
-                     NS_ConvertUTF16toUTF8(policy).get()));
+                   ("Document has CSP[%d]: %s", i,
+                   NS_ConvertUTF16toUTF8(policy).get()));
+          }
+        }
+      }
 #endif
-            // obtain the enforcement decision
-            // (don't pass aExtra, we use that slot for redirects)
-            csp->ShouldLoad(aContentType,
-                            aContentLocation,
-                            aRequestOrigin,
-                            aRequestContext,
-                            aMimeTypeGuess,
-                            nullptr,
-                            aDecision);
-        }
+      // obtain the enforcement decision
+      // (don't pass aExtra, we use that slot for redirects)
+      csp->ShouldLoad(aContentType,
+                      aContentLocation,
+                      aRequestOrigin,
+                      aRequestContext,
+                      aMimeTypeGuess,
+                      nullptr,
+                      aDecision);
     }
+  }
 #ifdef PR_LOGGING
-    else {
-        nsAutoCString uriSpec;
-        aContentLocation->GetSpec(uriSpec);
-        PR_LOG(gCspPRLog, PR_LOG_DEBUG,
-            ("COULD NOT get nsINode for location: %s", uriSpec.get()));
-    }
+  else {
+    nsAutoCString uriSpec;
+    aContentLocation->GetSpec(uriSpec);
+    PR_LOG(gCspPRLog, PR_LOG_DEBUG,
+           ("COULD NOT get nsINode for location: %s", uriSpec.get()));
+  }
 #endif
 
-    return NS_OK;
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 CSPService::ShouldProcess(uint32_t         aContentType,
                           nsIURI           *aContentLocation,
                           nsIURI           *aRequestOrigin,
                           nsISupports      *aRequestContext,
                           const nsACString &aMimeTypeGuess,
                           nsISupports      *aExtra,
                           nsIPrincipal     *aRequestPrincipal,
                           int16_t          *aDecision)
 {
-    if (!aContentLocation)
-        return NS_ERROR_FAILURE;
+  if (!aContentLocation)
+    return NS_ERROR_FAILURE;
 
-    // default decision is to accept the item
-    *aDecision = nsIContentPolicy::ACCEPT;
+  // default decision is to accept the item
+  *aDecision = nsIContentPolicy::ACCEPT;
 
-    // No need to continue processing if CSP is disabled
-    if (!sCSPEnabled)
-        return NS_OK;
+  // No need to continue processing if CSP is disabled
+  if (!sCSPEnabled)
+    return NS_OK;
 
-    // find the nsDocument that initiated this request and see if it has a
-    // CSP policy object
-    nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext));
-    nsCOMPtr<nsIPrincipal> principal;
-    nsCOMPtr<nsIContentSecurityPolicy> csp;
-    if (node) {
-        principal = node->NodePrincipal();
-        principal->GetCsp(getter_AddRefs(csp));
+  // find the nsDocument that initiated this request and see if it has a
+  // CSP policy object
+  nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext));
+  nsCOMPtr<nsIPrincipal> principal;
+  nsCOMPtr<nsIContentSecurityPolicy> csp;
+  if (node) {
+    principal = node->NodePrincipal();
+    principal->GetCsp(getter_AddRefs(csp));
 
-        if (csp) {
+    if (csp) {
 #ifdef PR_LOGGING
+      {
+        int numPolicies = 0;
+        nsresult rv = csp->GetPolicyCount(&numPolicies);
+        if (NS_SUCCEEDED(rv)) {
+          for (int i=0; i<numPolicies; i++) {
             nsAutoString policy;
-            csp->GetPolicy(policy);
+            csp->GetPolicy(i, policy);
             PR_LOG(gCspPRLog, PR_LOG_DEBUG,
-                  ("shouldProcess - document has policy: %s",
-                    NS_ConvertUTF16toUTF8(policy).get()));
+                   ("shouldProcess - document has policy[%d]: %s", i,
+                   NS_ConvertUTF16toUTF8(policy).get()));
+          }
+        }
+      }
 #endif
-            // obtain the enforcement decision
-            csp->ShouldProcess(aContentType,
-                               aContentLocation,
-                               aRequestOrigin,
-                               aRequestContext,
-                               aMimeTypeGuess,
-                               aExtra,
-                               aDecision);
-        }
+      // obtain the enforcement decision
+      csp->ShouldProcess(aContentType,
+                         aContentLocation,
+                         aRequestOrigin,
+                         aRequestContext,
+                         aMimeTypeGuess,
+                         aExtra,
+                         aDecision);
     }
+  }
 #ifdef PR_LOGGING
-    else {
-        nsAutoCString uriSpec;
-        aContentLocation->GetSpec(uriSpec);
-        PR_LOG(gCspPRLog, PR_LOG_DEBUG,
-            ("COULD NOT get nsINode for location: %s", uriSpec.get()));
-    }
+  else {
+    nsAutoCString uriSpec;
+    aContentLocation->GetSpec(uriSpec);
+    PR_LOG(gCspPRLog, PR_LOG_DEBUG,
+           ("COULD NOT get nsINode for location: %s", uriSpec.get()));
+  }
 #endif
-    return NS_OK;
+  return NS_OK;
 }
 
 /* nsIChannelEventSink implementation */
 NS_IMETHODIMP
 CSPService::AsyncOnChannelRedirect(nsIChannel *oldChannel,
                                    nsIChannel *newChannel,
                                    uint32_t flags,
                                    nsIAsyncVerifyRedirectCallback *callback)
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -2453,16 +2453,40 @@ nsDocument::SendToConsole(nsCOMArray<nsI
 
     nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
                                     NS_ConvertUTF16toUTF8(category),
                                     this, nsContentUtils::eSECURITY_PROPERTIES,
                                     NS_ConvertUTF16toUTF8(messageTag).get());
   }
 }
 
+static nsresult
+AppendCSPFromHeader(nsIContentSecurityPolicy* csp, const nsAString& aHeaderValue,
+                    nsIURI* aSelfURI, bool aReportOnly, bool aSpecCompliant)
+{
+  // Need to tokenize the header value since multiple headers could be
+  // concatenated into one comma-separated list of policies.
+  // See RFC2616 section 4.2 (last paragraph)
+  nsresult rv = NS_OK;
+  nsCharSeparatedTokenizer tokenizer(aHeaderValue, ',');
+  while (tokenizer.hasMoreTokens()) {
+      const nsSubstring& policy = tokenizer.nextToken();
+      rv = csp->AppendPolicy(policy, aSelfURI, aReportOnly, aSpecCompliant);
+      NS_ENSURE_SUCCESS(rv, rv);
+#ifdef PR_LOGGING
+      {
+        PR_LOG(gCspPRLog, PR_LOG_DEBUG,
+                ("CSP refined with policy: \"%s\"",
+                NS_ConvertUTF16toUTF8(policy).get()));
+      }
+#endif
+  }
+  return NS_OK;
+}
+
 nsresult
 nsDocument::InitCSP(nsIChannel* aChannel)
 {
   nsCOMPtr<nsIContentSecurityPolicy> csp;
   if (!CSPService::sCSPEnabled) {
 #ifdef PR_LOGGING
     PR_LOG(gCspPRLog, PR_LOG_DEBUG,
            ("CSP is disabled, skipping CSP init for document %p", this));
@@ -2497,25 +2521,33 @@ nsDocument::InitCSP(nsIChannel* aChannel
   NS_ConvertASCIItoUTF16 cspOldROHeaderValue(tCspOldROHeaderValue);
 
   // Only use the CSP 1.0 spec compliant headers if a pref to do so
   // is set. This lets us turn on the 1.0 parser per platform. This
   // pref is also set by the tests for 1.0 spec compliant CSP.
   bool specCompliantEnabled =
     Preferences::GetBool("security.csp.speccompliant");
 
+  // If spec compliant pref isn't set, pretend we never got these headers.
   if ((!cspHeaderValue.IsEmpty() || !cspROHeaderValue.IsEmpty()) &&
        !specCompliantEnabled) {
-    // If spec compliant pref isn't set, pretend we never got
-    // these headers.
-    if (!specCompliantEnabled) {
-      PR_LOG(gCspPRLog, PR_LOG_DEBUG,
-             ("Got spec compliant CSP headers but pref was not set"));
-      cspHeaderValue.Truncate();
-      cspROHeaderValue.Truncate();
+    PR_LOG(gCspPRLog, PR_LOG_DEBUG,
+            ("Got spec compliant CSP headers but pref was not set"));
+    cspHeaderValue.Truncate();
+    cspROHeaderValue.Truncate();
+  }
+
+  // If the old header is present, warn that it will be deprecated.
+  if (!cspOldHeaderValue.IsEmpty() || !cspOldROHeaderValue.IsEmpty()) {
+    mCSPWebConsoleErrorQueue.Add("OldCSPHeaderDeprecated");
+
+    // Also, if the new headers AND the old headers were present, warn
+    // that the old headers will be ignored.
+    if (!cspHeaderValue.IsEmpty() || !cspROHeaderValue.IsEmpty()) {
+      mCSPWebConsoleErrorQueue.Add("BothCSPHeadersPresent");
     }
   }
 
   // Figure out if we need to apply an app default CSP or a CSP from an app manifest
   nsIPrincipal* principal = NodePrincipal();
 
   uint16_t appStatus = principal->GetAppStatus();
   bool applyAppDefaultCSP = appStatus == nsIPrincipal::APP_STATUS_PRIVILEGED ||
@@ -2566,125 +2598,70 @@ nsDocument::InitCSP(nsIChannel* aChannel
   if (NS_FAILED(rv)) {
 #ifdef PR_LOGGING
     PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("Failed to create CSP object: %x", rv));
 #endif
     return rv;
   }
 
   // used as a "self" identifier for the CSP.
-  nsCOMPtr<nsIURI> chanURI;
-  aChannel->GetURI(getter_AddRefs(chanURI));
+  nsCOMPtr<nsIURI> selfURI;
+  aChannel->GetURI(getter_AddRefs(selfURI));
 
   // Store the request context for violation reports
   csp->ScanRequestData(httpChannel);
 
-  // The CSP is refined in the following order:
-  // 1. Default app CSP, if applicable
-  // 2. App manifest CSP, if provided
-  // 3. HTTP header CSP, if provided
-  // Note that since each application of refinePolicy is a set intersection,
-  // the order in which multiple CSP's are refined does not matter.
-
+  // ----- if the doc is an app and we want a default CSP, apply it.
   if (applyAppDefaultCSP) {
     nsAdoptingString appCSP;
     if (appStatus ==  nsIPrincipal::APP_STATUS_PRIVILEGED) {
       appCSP = Preferences::GetString("security.apps.privileged.CSP.default");
       NS_ASSERTION(appCSP, "App, but no default CSP in security.apps.privileged.CSP.default");
     } else if (appStatus == nsIPrincipal::APP_STATUS_CERTIFIED) {
       appCSP = Preferences::GetString("security.apps.certified.CSP.default");
       NS_ASSERTION(appCSP, "App, but no default CSP in security.apps.certified.CSP.default");
     }
 
-    if (appCSP)
+    if (appCSP) {
       // Use the 1.0 CSP parser for apps if the pref to do so is set.
-      csp->RefinePolicy(appCSP, chanURI, specCompliantEnabled);
-  }
-
+      csp->AppendPolicy(appCSP, selfURI, false, specCompliantEnabled);
+    }
+  }
+
+  // ----- if the doc is an app and specifies a CSP in its manifest, apply it.
   if (applyAppManifestCSP) {
     // Use the 1.0 CSP parser for apps if the pref to do so is set.
-    csp->RefinePolicy(appManifestCSP, chanURI, specCompliantEnabled);
+    csp->AppendPolicy(appManifestCSP, selfURI, false, specCompliantEnabled);
   }
 
   // While we are supporting both CSP 1.0 and the x- headers, the 1.0 headers
-  // take priority.  If any spec-compliant headers are present, the x- headers
-  // are ignored, and the spec compliant parser is used.
-  bool cspSpecCompliant = (!cspHeaderValue.IsEmpty() || !cspROHeaderValue.IsEmpty());
-
-  // If the old header is present, warn that it will be deprecated.
-  if (!cspOldHeaderValue.IsEmpty() || !cspOldROHeaderValue.IsEmpty()) {
-    mCSPWebConsoleErrorQueue.Add("OldCSPHeaderDeprecated");
-
-    // Also, if the new headers AND the old headers were present, warn
-    // that the old headers will be ignored.
-    if (cspSpecCompliant) {
-      mCSPWebConsoleErrorQueue.Add("BothCSPHeadersPresent");
-    }
-  }
+  // can coexist with x- headers.  If both exist, they're both enforced, but
+  // there's a warning posted in the web console that the x-headers are going
+  // away.
 
   // ----- if there's a full-strength CSP header, apply it.
-  bool applyCSPFromHeader =
-    (( cspSpecCompliant && !cspHeaderValue.IsEmpty()) ||
-     (!cspSpecCompliant && !cspOldHeaderValue.IsEmpty()));
-
-  if (applyCSPFromHeader) {
-    // Need to tokenize the header value since multiple headers could be
-    // concatenated into one comma-separated list of policies.
-    // See RFC2616 section 4.2 (last paragraph)
-    nsCharSeparatedTokenizer tokenizer(cspSpecCompliant ?
-                                       cspHeaderValue :
-                                       cspOldHeaderValue, ',');
-    while (tokenizer.hasMoreTokens()) {
-        const nsSubstring& policy = tokenizer.nextToken();
-        csp->RefinePolicy(policy, chanURI, cspSpecCompliant);
-#ifdef PR_LOGGING
-        {
-          PR_LOG(gCspPRLog, PR_LOG_DEBUG,
-                 ("CSP refined with policy: \"%s\"",
-                  NS_ConvertUTF16toUTF8(policy).get()));
-        }
-#endif
-    }
-  }
-
-  // ----- if there's a report-only CSP header, apply it
-  if (( cspSpecCompliant && !cspROHeaderValue.IsEmpty()) ||
-      (!cspSpecCompliant && !cspOldROHeaderValue.IsEmpty())) {
-    // post a warning and skip report-only CSP when both read only and regular
-    // CSP policies are present since CSP only allows one policy and it can't
-    // be partially report-only.
-    if (applyAppDefaultCSP || applyCSPFromHeader) {
-      mCSPWebConsoleErrorQueue.Add("ReportOnlyCSPIgnored");
-#ifdef PR_LOGGING
-      PR_LOG(gCspPRLog, PR_LOG_DEBUG,
-              ("Skipped report-only CSP init for document %p because another, enforced policy is set", this));
-#endif
-    } else {
-      // we can apply the report-only policy because there's no other CSP
-      // applied.
-      csp->SetReportOnlyMode(true);
-
-      // Need to tokenize the header value since multiple headers could be
-      // concatenated into one comma-separated list of policies.
-      // See RFC2616 section 4.2 (last paragraph)
-      nsCharSeparatedTokenizer tokenizer(cspSpecCompliant ?
-                                         cspROHeaderValue :
-                                         cspOldROHeaderValue, ',');
-      while (tokenizer.hasMoreTokens()) {
-        const nsSubstring& policy = tokenizer.nextToken();
-        csp->RefinePolicy(policy, chanURI, cspSpecCompliant);
-#ifdef PR_LOGGING
-        {
-          PR_LOG(gCspPRLog, PR_LOG_DEBUG,
-                  ("CSP (report-only) refined with policy: \"%s\"",
-                    NS_ConvertUTF16toUTF8(policy).get()));
-        }
-#endif
-      }
-    }
+  if (!cspOldHeaderValue.IsEmpty()) {
+    rv = AppendCSPFromHeader(csp, cspOldHeaderValue, selfURI, false, false);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (!cspHeaderValue.IsEmpty()) {
+    rv = AppendCSPFromHeader(csp, cspHeaderValue, selfURI, false, true);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  // ----- if there's a report-only CSP header, apply it.
+  if (!cspOldROHeaderValue.IsEmpty()) {
+    rv = AppendCSPFromHeader(csp, cspOldROHeaderValue, selfURI, true, false);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (!cspROHeaderValue.IsEmpty()) {
+    rv = AppendCSPFromHeader(csp, cspROHeaderValue, selfURI, true, true);
+    NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // ----- Enforce frame-ancestor policy on any applied policies
   nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocumentContainer);
   if (docShell) {
     bool safeAncestry = false;
 
     // PermitsAncestry sends violation reports when necessary
--- a/content/base/test/csp/Makefile.in
+++ b/content/base/test/csp/Makefile.in
@@ -87,16 +87,21 @@ MOCHITEST_FILES := \
   test_CSP_bug885433.html \
   file_CSP_bug885433_allows.html \
   file_CSP_bug885433_allows.html^headers^ \
   file_CSP_bug885433_blocks.html \
   file_CSP_bug885433_blocks.html^headers^ \
   test_CSP_bug888172.html \
   file_CSP_bug888172.html \
   file_CSP_bug888172.sjs \
+  test_bug836922_npolicies.html \
+  file_bug836922_npolicies.html \
+  file_bug836922_npolicies.html^headers^ \
+  file_bug836922_npolicies_violation.sjs \
+  file_bug836922_npolicies_ro_violation.sjs \
   $(NULL)
 
 MOCHITEST_CHROME_FILES := \
   test_csp_bug768029.html \
   test_csp_bug773891.html \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_bug836922_npolicies.html
@@ -0,0 +1,15 @@
+<html>
+  <head>
+    <link rel='stylesheet' type='text/css'
+          href='/tests/content/base/test/csp/file_CSP.sjs?testid=css_self&type=text/css' />
+    <link rel='stylesheet' type='text/css'
+          href='http://example.com/tests/content/base/test/csp/file_CSP.sjs?testid=css_examplecom&type=text/css' />
+
+  </head>
+  <body>
+    <img src="/tests/content/base/test/csp/file_CSP.sjs?testid=img_self&type=img/png"> </img>
+    <img src="http://example.com/tests/content/base/test/csp/file_CSP.sjs?testid=img_examplecom&type=img/png"> </img>
+    <script src='/tests/content/base/test/csp/file_CSP.sjs?testid=script_self&type=text/javascript'></script>
+
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_bug836922_npolicies.html^headers^
@@ -0,0 +1,2 @@
+content-security-policy: default-src 'self'; img-src 'none'; report-uri http://mochi.test:8888/tests/content/base/test/csp/file_bug836922_npolicies_violation.sjs
+content-security-policy-report-only: default-src *; img-src 'self'; script-src 'none'; report-uri http://mochi.test:8888/tests/content/base/test/csp/file_bug836922_npolicies_ro_violation.sjs
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_bug836922_npolicies_ro_violation.sjs
@@ -0,0 +1,53 @@
+// SJS file that receives violation reports and then responds with nothing.
+
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+                              "nsIBinaryInputStream",
+                              "setInputStream");
+
+const STATE_KEY = "bug836922_ro_violations";
+
+function handleRequest(request, response)
+{
+  var query = {};
+  request.queryString.split('&').forEach(function (val) {
+    var [name, value] = val.split('=');
+    query[name] = unescape(value);
+  });
+
+  if ('results' in query) {
+    // if asked for the received data, send it.
+    response.setHeader("Content-Type", "text/javascript", false);
+    if (getState(STATE_KEY)) {
+      response.write(getState(STATE_KEY));
+    } else {
+      // no state has been recorded.
+      response.write(JSON.stringify({}));
+    }
+  } else if ('reset' in query) {
+    //clear state
+    setState(STATE_KEY, JSON.stringify(null));
+  } else {
+    // ... otherwise, just respond "ok".
+    response.write("null");
+
+    var bodystream = new BinaryInputStream(request.bodyInputStream);
+    var avail;
+    var bytes = [];
+    while ((avail = bodystream.available()) > 0)
+      Array.prototype.push.apply(bytes, bodystream.readByteArray(avail));
+
+    var data = String.fromCharCode.apply(null, bytes);
+
+    // figure out which test was violating a policy
+    var testpat = new RegExp("testid=([a-z0-9_]+)");
+    var testid = testpat.exec(data)[1];
+
+    // store the violation in the persistent state
+    var s = JSON.parse(getState(STATE_KEY) || "{}");
+    s[testid] ? s[testid]++ : s[testid] = 1;
+    setState(STATE_KEY, JSON.stringify(s));
+  }
+}
+
+
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_bug836922_npolicies_violation.sjs
@@ -0,0 +1,59 @@
+// SJS file that receives violation reports and then responds with nothing.
+
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+                              "nsIBinaryInputStream",
+                              "setInputStream");
+
+const STATE = "bug836922_violations";
+
+function handleRequest(request, response)
+{
+  var query = {};
+  request.queryString.split('&').forEach(function (val) {
+    var [name, value] = val.split('=');
+    query[name] = unescape(value);
+  });
+
+
+  if ('results' in query) {
+    // if asked for the received data, send it.
+    response.setHeader("Content-Type", "text/javascript", false);
+    if (getState(STATE)) {
+      response.write(getState(STATE));
+    } else {
+      // no state has been recorded.
+      response.write(JSON.stringify({}));
+    }
+  } else if ('reset' in query) {
+    //clear state
+    setState(STATE, JSON.stringify(null));
+  } else {
+    // ... otherwise, just respond "ok".
+    response.write("null");
+
+    var bodystream = new BinaryInputStream(request.bodyInputStream);
+    var avail;
+    var bytes = [];
+    while ((avail = bodystream.available()) > 0)
+      Array.prototype.push.apply(bytes, bodystream.readByteArray(avail));
+
+    var data = String.fromCharCode.apply(null, bytes);
+
+    // figure out which test was violating a policy
+    var testpat = new RegExp("testid=([a-z0-9_]+)");
+    var testid = testpat.exec(data)[1];
+
+    // store the violation in the persistent state
+    var s = getState(STATE);
+    if (!s) s = "{}";
+    s = JSON.parse(s);
+    if (!s) s = {};
+
+    if (!s[testid]) s[testid] = 0;
+    s[testid]++;
+    setState(STATE, JSON.stringify(s));
+  }
+}
+
+
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/test_bug836922_npolicies.html
@@ -0,0 +1,255 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Content Security Policy multiple policy support (regular and Report-Only mode)</title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<iframe style="width:200px;height:200px;" id='cspframe'></iframe>
+<script class="testbody" type="text/javascript">
+
+var path = "/tests/content/base/test/csp/";
+
+// These are test results: verified indicates whether or not the test has run.
+// true/false is the pass/fail result.
+window.loads = {
+  css_self: {expected: true, verified: false},
+  css_examplecom: {expected: false, verified: false},
+  img_self: {expected: false, verified: false},
+  img_examplecom: {expected: false, verified: false},
+  script_self: {expected: true, verified: false},
+};
+
+window.violation_reports = {
+  css_self:
+  {expected: 0, expected_ro: 0},  /* totally fine */
+  css_examplecom:
+  {expected: 1, expected_ro: 0},  /* violates enforced CSP */
+  img_self:
+  {expected: 1, expected_ro: 0},  /* violates enforced CSP */
+  img_examplecom:
+  {expected: 1, expected_ro: 1},  /* violates both CSPs */
+  script_self:
+  {expected: 0, expected_ro: 1},  /* violates report-only */
+};
+
+// This is used to watch the blocked data bounce off CSP and allowed data
+// get sent out to the wire.  This also watches for violation reports to go out.
+function examiner() {
+  SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+  SpecialPowers.addObserver(this, "http-on-modify-request", false);
+}
+examiner.prototype  = {
+  observe: function(subject, topic, data) {
+    // subject should be an nsURI, and should be either allowed or blocked.
+    if(!SpecialPowers.can_QI(subject))
+       return;
+
+    var testpat = new RegExp("testid=([a-z0-9_]+)");
+
+    if (topic === "http-on-modify-request") {
+      var asciiSpec = SpecialPowers.getPrivilegedProps(
+                        SpecialPowers.do_QueryInterface(subject, "nsIHttpChannel"),
+                        "URI.asciiSpec");
+      if (!testpat.test(asciiSpec)) return;
+      var testid = testpat.exec(asciiSpec)[1];
+
+      // violation reports don't come through here, but the requested resources do
+      // if the test has already finished, move on.  Some things throw multiple
+      // requests (preloads and such)
+      try {
+        if (window.loads[testid].verified) return;
+      } catch(e) { return; }
+
+      // these are requests that were allowed by CSP
+      var testid = testpat.exec(asciiSpec)[1];
+      window.testResult(testid, 'allowed', asciiSpec + " allowed by csp");
+    }
+
+    if(topic === "csp-on-violate-policy") {
+      // if the violated policy was report-only, the resource will still be
+      // loaded even if this topic is notified.
+      var asciiSpec = SpecialPowers.getPrivilegedProps(
+                        SpecialPowers.do_QueryInterface(subject, "nsIURI"),
+                        "asciiSpec");
+      if (!testpat.test(asciiSpec)) return;
+      var testid = testpat.exec(asciiSpec)[1];
+
+      // if the test has already finished, move on.
+      try {
+        if (window.loads[testid].verified) return;
+      } catch(e) { return; }
+
+      // record the ones that were supposed to be blocked, but don't use this
+      // as an indicator for tests that are not blocked but do generate reports.
+      // We skip recording the result if the load is expected since a
+      // report-only policy will generate a request *and* a violation note.
+      if (!window.loads[testid].expected) {
+        window.testResult(testid,
+                          'blocked',
+                          asciiSpec + " blocked by \"" + data + "\"");
+      }
+    }
+
+    // if any test is unverified, keep waiting
+    for (var v in window.loads) {
+      if(!window.loads[v].verified) {
+        return;
+      }
+    }
+
+    window.bug836922examiner.remove();
+    window.resultPoller.pollForFinish();
+  },
+
+  // must eventually call this to remove the listener,
+  // or mochitests might get borked.
+  remove: function() {
+    SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+    SpecialPowers.removeObserver(this, "http-on-modify-request");
+  }
+}
+window.bug836922examiner = new examiner();
+
+
+// Poll for results and see if enough reports came in.  Keep trying
+// for a few seconds before failing with lack of reports.
+// Have to do this because there's a race between the async reporting
+// and this test finishing, and we don't want to win the race.
+window.resultPoller = {
+
+  POLL_ATTEMPTS_LEFT: 14,
+
+  pollForFinish:
+  function() {
+    var vr = resultPoller.tallyReceivedReports();
+    if (resultPoller.verifyReports(vr, resultPoller.POLL_ATTEMPTS_LEFT < 1)) {
+      // report success condition.
+      resultPoller.resetReportServer();
+      SimpleTest.finish();
+    } else {
+      resultPoller.POLL_ATTEMPTS_LEFT--;
+      // try again unless we reached the threshold.
+      setTimeout(resultPoller.pollForFinish, 100);
+    }
+  },
+
+  resetReportServer:
+  function() {
+    var xhr = new XMLHttpRequest();
+    var xhr_ro = new XMLHttpRequest();
+    xhr.open("GET", "file_bug836922_npolicies_violation.sjs?reset", false);
+    xhr_ro.open("GET", "file_bug836922_npolicies_ro_violation.sjs?reset", false);
+    xhr.send(null);
+    xhr_ro.send(null);
+  },
+
+  tallyReceivedReports:
+  function() {
+    var xhr = new XMLHttpRequest();
+    var xhr_ro = new XMLHttpRequest();
+    xhr.open("GET", "file_bug836922_npolicies_violation.sjs?results", false);
+    xhr_ro.open("GET", "file_bug836922_npolicies_ro_violation.sjs?results", false);
+    xhr.send(null);
+    xhr_ro.send(null);
+
+    var received = JSON.parse(xhr.responseText);
+    var received_ro = JSON.parse(xhr_ro.responseText);
+
+    var results = {enforced: {}, reportonly: {}};
+    for (var r in window.violation_reports) {
+      results.enforced[r] = 0;
+      results.reportonly[r] = 0;
+    }
+
+    for (var r in received) {
+      results.enforced[r] += received[r];
+    }
+    for (var r in received_ro) {
+      results.reportonly[r] += received_ro[r];
+    }
+
+    return results;
+  },
+
+  verifyReports:
+  function(receivedCounts, lastAttempt) {
+    for (var r in window.violation_reports) {
+      var exp = window.violation_reports[r].expected;
+      var exp_ro = window.violation_reports[r].expected_ro;
+      var rec = receivedCounts.enforced[r];
+      var rec_ro = receivedCounts.reportonly[r];
+
+      // if this test breaks, these are helpful dumps:
+      //dump(">>> Verifying " + r + "\n");
+      //dump("  > Expected: " + exp + " / " + exp_ro + " (ro)\n");
+      //dump("  > Received: " + rec + " / " + rec_ro + " (ro) \n");
+
+      // in all cases, we're looking for *at least* the expected number of
+      // reports of each type (there could be more in some edge cases).
+      // If there are not enough, we keep waiting and poll the server again
+      // later.  If there are enough, we can successfully finish.
+
+      if (exp == 0)
+        is(rec, 0,
+          "Expected zero enforced-policy violation " +
+          "reports for " + r + ", got " + rec);
+      else if (lastAttempt)
+        ok(rec >= exp,
+          "Received (" + rec + "/" + exp + ") " +
+          "enforced-policy reports for " + r);
+      else if (rec < exp)
+        return false; // continue waiting for more
+
+      if(exp_ro == 0)
+        is(rec_ro, 0,
+          "Expected zero report-only-policy violation " +
+          "reports for " + r + ", got " + rec_ro);
+      else if (lastAttempt)
+        ok(rec_ro >= exp_ro,
+          "Received (" + rec_ro + "/" + exp_ro + ") " +
+          "report-only-policy reports for " + r);
+      else if (rec_ro < exp_ro)
+        return false; // continue waiting for more
+    }
+
+    // if we complete the loop, we've found all of the violation
+    // reports we expect.
+    if (lastAttempt) return true;
+
+    // Repeat successful tests once more to record successes via ok()
+    return resultPoller.verifyReports(receivedCounts, true);
+  }
+};
+
+window.testResult = function(testname, result, msg) {
+  // otherwise, make sure the allowed ones are expected and blocked ones are not.
+  if (window.loads[testname].expected) {
+    is(result, 'allowed', ">> " + msg);
+  } else {
+    is(result, 'blocked', ">> " + msg);
+  }
+  window.loads[testname].verified = true;
+}
+
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv(
+  {'set':[["security.csp.speccompliant", true]]},
+   function() {
+    // save this for last so that our listeners are registered.
+    // ... this loads the testbed of good and bad requests.
+    document.getElementById('cspframe').src = 'http://mochi.test:8888' + path + 'file_bug836922_npolicies.html';
+  });
+
+</script>
+</pre>
+</body>
+</html>
--- a/content/base/test/unit/test_bug558431.js
+++ b/content/base/test/unit/test_bug558431.js
@@ -62,17 +62,17 @@ listener.prototype = {
     // make sure that we have the full document content, guaranteeing that
     // the document channel has been resumed, before we do the comparisons
     if (this.buffer == CSP_DOC_BODY) {
 
       // need to re-grab cspr since it may have changed inside the document's
       // nsIContentSecurityPolicy instance.  The problem is, this cspr_str is a
       // string and not a policy due to the way it's exposed from
       // nsIContentSecurityPolicy, so we have to re-parse it.
-      let cspr_str = this._csp.policy;
+      let cspr_str = this._csp.getPolicy(0);
       let cspr = CSPRep.fromString(cspr_str, mkuri(DOCUMENT_URI));
 
       // and in reparsing it, we lose the 'self' relationships, so need to also
       // reparse the static one (or find a way to resolve 'self' in the parsed
       // policy when doing comparisons).
       let cspr_static_str = this._cspr_static.toString();
       let cspr_static_reparse = CSPRep.fromString(cspr_static_str, mkuri(DOCUMENT_URI));
 
--- a/content/base/test/unit/test_cspreports.js
+++ b/content/base/test/unit/test_cspreports.js
@@ -74,20 +74,18 @@ function makeTest(id, expectedJSON, useR
   var selfchan = NetUtil.newChannel(selfuri);
 
   dump("Created test " + id + " : " + policy + "\n\n");
 
   // make the reports seem authentic by "binding" them to a channel.
   csp.scanRequestData(selfchan);
 
   // Load up the policy
-  csp.refinePolicy(policy, selfuri, false);
-
   // set as report-only if that's the case
-  if (useReportOnlyPolicy) csp.reportOnlyMode = true;
+  csp.appendPolicy(policy, selfuri, useReportOnlyPolicy, false);
 
   // prime the report server
   var handler = makeReportHandler("/test" + id, "Test " + id, expectedJSON);
   httpServer.registerPathHandler("/test" + id, handler);
 
   //trigger the violation
   callback(csp);
 }
@@ -95,85 +93,94 @@ function makeTest(id, expectedJSON, useR
 function run_test() {
   var selfuri = NetUtil.newURI(REPORT_SERVER_URI +
                                ":" + REPORT_SERVER_PORT +
                                "/foo/self");
 
   // test that inline script violations cause a report.
   makeTest(0, {"blocked-uri": "self"}, false,
       function(csp) {
-        let inlineOK = true, oReportViolation = {};
+        let inlineOK = true, oReportViolation = {'value': false};
         inlineOK = csp.getAllowsInlineScript(oReportViolation);
 
         // this is not a report only policy, so it better block inline scripts
         do_check_false(inlineOK);
         // ... and cause reports to go out
         do_check_true(oReportViolation.value);
 
-        // force the logging, since the getter doesn't.
-        csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
-                                selfuri.asciiSpec,
-                                "script sample",
-                                0);
+        if (oReportViolation.value) {
+          // force the logging, since the getter doesn't.
+          csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
+                                  selfuri.asciiSpec,
+                                  "script sample",
+                                  0);
+        }
       });
 
   // test that eval violations cause a report.
   makeTest(1, {"blocked-uri": "self"}, false,
       function(csp) {
-        let evalOK = true, oReportViolation = {};
+        let evalOK = true, oReportViolation = {'value': false};
         evalOK = csp.getAllowsEval(oReportViolation);
 
         // this is not a report only policy, so it better block eval
         do_check_false(evalOK);
         // ... and cause reports to go out
         do_check_true(oReportViolation.value);
 
-        // force the logging, since the getter doesn't.
-        csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
-                                selfuri.asciiSpec,
-                                "script sample",
-                                1);
+        if (oReportViolation.value) {
+          // force the logging, since the getter doesn't.
+          csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_EVAL,
+                                  selfuri.asciiSpec,
+                                  "script sample",
+                                  1);
+        }
       });
 
   makeTest(2, {"blocked-uri": "http://blocked.test/foo.js"}, false,
       function(csp) {
         // shouldLoad creates and sends out the report here.
         csp.shouldLoad(Ci.nsIContentPolicy.TYPE_SCRIPT,
                       NetUtil.newURI("http://blocked.test/foo.js"),
                       null, null, null, null);
       });
 
   // test that inline script violations cause a report in report-only policy
   makeTest(3, {"blocked-uri": "self"}, true,
       function(csp) {
-        let inlineOK = true, oReportViolation = {};
+        let inlineOK = true, oReportViolation = {'value': false};
         inlineOK = csp.getAllowsInlineScript(oReportViolation);
 
         // this is a report only policy, so it better allow inline scripts
         do_check_true(inlineOK);
-        // ... but still cause reports to go out
+
+        // ... and cause reports to go out
         do_check_true(oReportViolation.value);
 
-        // force the logging, since the getter doesn't.
-        csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
-                                selfuri.asciiSpec,
-                                "script sample",
-                                3);
+        if (oReportViolation.value) {
+          // force the logging, since the getter doesn't.
+          csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
+                                  selfuri.asciiSpec,
+                                  "script sample",
+                                  3);
+        }
       });
 
   // test that eval violations cause a report in report-only policy
   makeTest(4, {"blocked-uri": "self"}, true,
       function(csp) {
-        let evalOK = true, oReportViolation = {};
+        let evalOK = true, oReportViolation = {'value': false};
         evalOK = csp.getAllowsEval(oReportViolation);
 
         // this is a report only policy, so it better allow eval
         do_check_true(evalOK);
         // ... but still cause reports to go out
         do_check_true(oReportViolation.value);
 
-        // force the logging, since the getter doesn't.
-        csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
-                                selfuri.asciiSpec,
-                                "script sample",
-                                4);
+        if (oReportViolation.value) {
+          // force the logging, since the getter doesn't.
+          csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
+                                  selfuri.asciiSpec,
+                                  "script sample",
+                                  4);
+        }
       });
 }
--- a/content/base/test/unit/test_csputils.js
+++ b/content/base/test/unit/test_csputils.js
@@ -136,25 +136,16 @@ test(
     do_check_true( h.permits("a.b.c"));  //"CSPHost *.b.c should allow string a.b.c"
     do_check_false(h.permits("b.c"));    //"CSPHost *.b.c should not allow string b.c"
     do_check_false(h.permits("a.a.c"));  //"CSPHost *.b.c should not allow string a.a.c"
     do_check_false(h2.permits(h));       //"CSPHost a.b.c should not allow CSPHost *.b.c"
     do_check_false(h2.permits("b.c"));   //"CSPHost a.b.c should not allow string b.c"
     do_check_true( h2.permits("a.b.c")); //"CSPHost a.b.c should allow string a.b.c"
   });
 
-test(
-    function test_CSPHost_intersectWith() {
-      var h = CSPHost.fromString("*.b.c");
-      //"*.a.b.c ^ *.b.c should be *.a.b.c"
-      do_check_eq("*.a.b.c", h.intersectWith(CSPHost.fromString("*.a.b.c")).toString());
-
-      //"*.b.c ^ *.d.e should not work (null)"
-      do_check_eq(null, h.intersectWith(CSPHost.fromString("*.d.e")));
-    });
 
 ///////////////////// Test the Source object //////////////////////
 
 test(
     function test_CSPSource_fromString() {
     // can't do these tests because "self" is not defined.
       //"basic source should not be null.");
       do_check_neq(null, CSPSource.fromString("a.com", undefined, "http://abc.com"));
@@ -312,52 +303,16 @@ test(
       do_check_false(allGarbageHostSourceList.permits("http://barbaz.com"));
 
       //"*.foo.com does not permit somerandom.foo.com"
       do_check_true(wildcardHostSourceList.permits("http://somerandom.foo.com"));
       //"*.foo.com permits all"
       do_check_false(wildcardHostSourceList.permits("http://barbaz.com"));
     });
 
-test(
-    function test_CSPSourceList_intersect() {
-      // for this test, 'self' values are irrelevant
-      // policy a /\ policy b intersects policies, not context (where 'self'
-      // values come into play)
-      var nullSourceList = CSPSourceList.fromString("'none'");
-      var simpleSourceList = CSPSourceList.fromString("http://a.com");
-      var doubleSourceList = CSPSourceList.fromString("https://foo.com http://bar.com:88");
-      var singleFooSourceList = CSPSourceList.fromString("https://foo.com");
-      var allSourceList = CSPSourceList.fromString("*");
-
-      //"Intersection of one source with 'none' source list should be none.");
-      do_check_true(nullSourceList.intersectWith(simpleSourceList).isNone());
-      //"Intersection of two sources with 'none' source list should be none.");
-      do_check_true(nullSourceList.intersectWith(doubleSourceList).isNone());
-      //"Intersection of '*' with 'none' source list should be none.");
-      do_check_true(nullSourceList.intersectWith(allSourceList).isNone());
-
-      //"Intersection of one source with '*' source list should be one source.");
-      do_check_equivalent(allSourceList.intersectWith(simpleSourceList),
-                          simpleSourceList);
-      //"Intersection of two sources with '*' source list should be two sources.");
-      do_check_equivalent(allSourceList.intersectWith(doubleSourceList),
-                          doubleSourceList);
-
-      //"Non-overlapping source lists should intersect to 'none'");
-      do_check_true(simpleSourceList.intersectWith(doubleSourceList).isNone());
-
-      //"subset and superset should intersect to subset.");
-      do_check_equivalent(singleFooSourceList,
-                          doubleSourceList.intersectWith(singleFooSourceList));
-
-      //TODO: write more tests?
-
-    });
-
 ///////////////////// Test the Whole CSP rep object //////////////////////
 
 test(
     function test_CSPRep_fromString() {
 
       // check default init
       //ASSERT(!(new CSPRep())._isInitialized, "Uninitialized rep thinks it is.")
 
@@ -675,17 +630,17 @@ test(function test_FrameAncestor_ignores
       do_check_true(cspr.permits(URI("http://self.com/foo"), SD.FRAME_ANCESTORS));
 
       // construct fake ancestry with CSP applied to the child.
       // [aChildUri] -> [aParentUri] -> (root/top)
       // and then test "permitsAncestry" on the child/self docshell.
       function testPermits(aChildUri, aParentUri, aContentType) {
         let cspObj = Cc["@mozilla.org/contentsecuritypolicy;1"]
                        .createInstance(Ci.nsIContentSecurityPolicy);
-        cspObj.refinePolicy(testPolicy, aChildUri, false);
+        cspObj.appendPolicy(testPolicy, aChildUri, false, false);
         let docshellparent = Cc["@mozilla.org/docshell;1"]
                                .createInstance(Ci.nsIDocShell);
         let docshellchild  = Cc["@mozilla.org/docshell;1"]
                                .createInstance(Ci.nsIDocShell);
         docshellparent.setCurrentURI(aParentUri);
         docshellchild.setCurrentURI(aChildUri);
         docshellparent.QueryInterface(Ci.nsIDocShellTreeNode)
                       .addChild(docshellchild);
@@ -904,82 +859,32 @@ test(
       do_check_false(p_all.equals(p_none));
       do_check_false(p_all.equals(p_one));
 
       do_check_true(p_all.permits("http://bar.com"));
       do_check_true(p_one.permits("http://bar.com"));
       do_check_false(p_none.permits("http://bar.com"));
     });
 
-test(
-    function test_bug783497_refinePolicyIssues() {
-
-      const firstPolicy = "allow 'self'; img-src 'self'; script-src 'self'; options 'bogus-option'";
-      const secondPolicy = "default-src 'none'; script-src 'self'";
-      var cspObj = Cc["@mozilla.org/contentsecuritypolicy;1"]
-                     .createInstance(Ci.nsIContentSecurityPolicy);
-      var selfURI = URI("http://self.com/");
-
-      function testPermits(aUri, aContentType) {
-        return cspObj.shouldLoad(aContentType, aUri, null, null, null, null)
-               == Ci.nsIContentPolicy.ACCEPT;
-      };
-
-      // everything is allowed by the default policy
-      do_check_true(testPermits(URI("http://self.com/foo.js"),
-                    Ci.nsIContentPolicy.TYPE_SCRIPT));
-      do_check_true(testPermits(URI("http://other.com/foo.js"),
-                    Ci.nsIContentPolicy.TYPE_SCRIPT));
-      do_check_true(testPermits(URI("http://self.com/foo.png"),
-                    Ci.nsIContentPolicy.TYPE_IMAGE));
-      do_check_true(testPermits(URI("http://other.com/foo.png"),
-                    Ci.nsIContentPolicy.TYPE_IMAGE));
-
-      // fold in the first policy
-      cspObj.refinePolicy(firstPolicy, selfURI, false);
-
-      // script-src and img-src are limited to self after the first policy
-      do_check_true(testPermits(URI("http://self.com/foo.js"),
-                    Ci.nsIContentPolicy.TYPE_SCRIPT));
-      do_check_false(testPermits(URI("http://other.com/foo.js"),
-                     Ci.nsIContentPolicy.TYPE_SCRIPT));
-      do_check_true(testPermits(URI("http://self.com/foo.png"),
-                    Ci.nsIContentPolicy.TYPE_IMAGE));
-      do_check_false(testPermits(URI("http://other.com/foo.png"),
-                     Ci.nsIContentPolicy.TYPE_IMAGE));
-
-      // fold in the second policy
-      cspObj.refinePolicy(secondPolicy, selfURI, false);
-
-      // script-src is self and img-src is none after the merge
-      do_check_true(testPermits(URI("http://self.com/foo.js"),
-                    Ci.nsIContentPolicy.TYPE_SCRIPT));
-      do_check_false(testPermits(URI("http://other.com/foo.js"),
-                     Ci.nsIContentPolicy.TYPE_SCRIPT));
-      do_check_false(testPermits(URI("http://self.com/foo.png"),
-                     Ci.nsIContentPolicy.TYPE_IMAGE));
-      do_check_false(testPermits(URI("http://other.com/foo.png"),
-                     Ci.nsIContentPolicy.TYPE_IMAGE));
-    });
 
 test(
     function test_bug764937_defaultSrcMissing() {
       var cspObjSpecCompliant = Cc["@mozilla.org/contentsecuritypolicy;1"]
                      .createInstance(Ci.nsIContentSecurityPolicy);
       var cspObjOld = Cc["@mozilla.org/contentsecuritypolicy;1"]
                      .createInstance(Ci.nsIContentSecurityPolicy);
       var selfURI = URI("http://self.com/");
 
       function testPermits(cspObj, aUri, aContentType) {
         return cspObj.shouldLoad(aContentType, aUri, null, null, null, null)
                == Ci.nsIContentPolicy.ACCEPT;
       };
 
       const policy = "script-src 'self'";
-      cspObjSpecCompliant.refinePolicy(policy, selfURI, true);
+      cspObjSpecCompliant.appendPolicy(policy, selfURI, false, true);
 
       // Spec-Compliant policy default-src defaults to *.
       // This means all images are allowed, and only 'self'
       // script is allowed.
       do_check_true(testPermits(cspObjSpecCompliant,
                                 URI("http://bar.com/foo.png"),
                                 Ci.nsIContentPolicy.TYPE_IMAGE));
       do_check_true(testPermits(cspObjSpecCompliant,
@@ -987,17 +892,17 @@ test(
                                 Ci.nsIContentPolicy.TYPE_IMAGE));
       do_check_true(testPermits(cspObjSpecCompliant,
                                 URI("http://self.com/foo.js"),
                                 Ci.nsIContentPolicy.TYPE_SCRIPT));
       do_check_false(testPermits(cspObjSpecCompliant,
                                  URI("http://bar.com/foo.js"),
                                  Ci.nsIContentPolicy.TYPE_SCRIPT));
 
-      cspObjOld.refinePolicy(policy, selfURI, false);
+      cspObjOld.appendPolicy(policy, selfURI, false, false);
 
       // non-Spec-Compliant policy default-src defaults to 'none'
       // This means all images are blocked, and so are all scripts (because the
       // policy is ignored and fails closed).
       do_check_false(testPermits(cspObjOld,
                                 URI("http://bar.com/foo.png"),
                                 Ci.nsIContentPolicy.TYPE_IMAGE));
       do_check_false(testPermits(cspObjOld,
--- a/content/test/unit/test_range.js
+++ b/content/test/unit/test_range.js
@@ -1,19 +1,16 @@
 /* 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/. */
 
 const C_i = Components.interfaces;
 
 const UNORDERED_TYPE = C_i.nsIDOMXPathResult.ANY_UNORDERED_NODE_TYPE;
 
-// Instantiate nsIDOMScriptObjectFactory so that DOMException is usable in xpcshell
-Components.classesByID["{9eb760f0-4380-11d2-b328-00805f8a3859}"].getService(C_i.nsISupports);
-
 /**
  * Determine if the data node has only ignorable white-space.
  *
  * @return nsIDOMNodeFilter.FILTER_SKIP if it does.
  * @return nsIDOMNodeFilter.FILTER_ACCEPT otherwise.
  */
 function isWhitespace(aNode) {
   return ((/\S/).test(aNode.nodeValue)) ?
new file mode 100644
--- /dev/null
+++ b/docshell/base/crashtests/914521.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="UTF-8">
+<script>
+
+function f()
+{
+    function spin() {
+        for (var i = 0; i < 8; ++i) {
+            var x = new XMLHttpRequest();
+            x.open('GET', 'data:text/html,' + i, false);
+            x.send();
+        }
+    }
+
+    window.addEventListener("popstate", spin, false);
+    window.close();
+    window.location = "#c";
+    finish();
+}
+
+function start()
+{
+    var html = "<script>" + f + "<\/script><body onload=f()>";
+    var win = window.open("data:text/html," + encodeURIComponent(html), null, "width=300,height=300");
+    win.finish = function() { document.documentElement.removeAttribute("class"); };
+}
+
+</script>
+</head>
+<body onload="start();"></body>
+</html>
--- a/docshell/base/crashtests/crashtests.list
+++ b/docshell/base/crashtests/crashtests.list
@@ -6,8 +6,9 @@ load 430628-1.html
 load 432114-1.html
 load 432114-2.html
 asserts-if(Android,2) load 436900-1.html
 asserts(0-3) load 436900-2.html # bug 566159
 load 500328-1.html
 load 514779-1.xhtml
 load 614499-1.html
 load 678872-1.html
+skip-if(Android||B2G) pref(dom.disable_open_during_load,false) load 914521.html
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -9209,28 +9209,33 @@ nsDocShell::InternalLoad(nsIURI * aURI,
             nsCOMPtr<nsIDocument> doc =
               do_GetInterface(GetAsSupports(this));
             NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
             doc->SetDocumentURI(aURI);
 
             SetDocCurrentStateObj(mOSHE);
 
             // Dispatch the popstate and hashchange events, as appropriate.
-            if (mScriptGlobal) {
+            //
+            // The event dispatch below can cause us to re-enter script and
+            // destroy the docshell, nulling out mScriptGlobal. Hold a stack
+            // reference to avoid null derefs. See bug 914521.
+            nsRefPtr<nsGlobalWindow> win = mScriptGlobal;
+            if (win) {
                 // Fire a hashchange event URIs differ, and only in their hashes.
                 bool doHashchange = sameExceptHashes && !curHash.Equals(newHash);
 
                 if (historyNavBetweenSameDoc || doHashchange) {
-                    mScriptGlobal->DispatchSyncPopState();
+                    win->DispatchSyncPopState();
                 }
 
                 if (doHashchange) {
                     // Make sure to use oldURI here, not mCurrentURI, because by
                     // now, mCurrentURI has changed!
-                    mScriptGlobal->DispatchAsyncHashchange(oldURI, aURI);
+                    win->DispatchAsyncHashchange(oldURI, aURI);
                 }
             }
 
             // Inform the favicon service that the favicon for oldURI also
             // applies to aURI.
             CopyFavicon(oldURI, aURI, mInPrivateBrowsing);
 
             return NS_OK;
--- a/dom/apps/src/AppsUtils.jsm
+++ b/dom/apps/src/AppsUtils.jsm
@@ -546,16 +546,20 @@ ManifestHelper.prototype = {
   get name() {
     return this._localeProp("name");
   },
 
   get description() {
     return this._localeProp("description");
   },
 
+  get type() {
+    return this._localeProp("type");
+  },
+
   get version() {
     return this._localeProp("version");
   },
 
   get launch_path() {
     return this._localeProp("launch_path");
   },
 
new file mode 100644
--- /dev/null
+++ b/dom/apps/src/InterAppComm.cpp
@@ -0,0 +1,25 @@
+/* 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 "InterAppComm.h"
+#include "nsContentUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsJSPrincipals.h"
+#include "mozilla/Preferences.h"
+#include "AccessCheck.h"
+
+using namespace mozilla::dom;
+
+/* static */ bool
+InterAppComm::EnabledForScope(JSContext* /* unused */, JSObject* aObj)
+{
+  // Disable the constructors if they're disabled by the preference for sure.
+  if (!Preferences::GetBool("dom.inter-app-communication-api.enabled", false)) {
+  	return false;
+  }
+
+  // Only expose the constructors to the chrome codes for Gecko internal uses.
+  // The content pages shouldn't be aware of the constructors.
+  return xpc::AccessCheck::isChrome(aObj);
+}
new file mode 100644
--- /dev/null
+++ b/dom/apps/src/InterAppComm.h
@@ -0,0 +1,24 @@
+/* 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_apps_InterAppComm_h
+#define mozilla_dom_apps_InterAppComm_h
+
+// Forward declarations.
+struct JSContext;
+class JSObject;
+
+namespace mozilla {
+namespace dom {
+
+class InterAppComm
+{
+public:
+  static bool EnabledForScope(JSContext* /* unused */, JSObject* aObj);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_apps_InterAppComm_h
new file mode 100644
--- /dev/null
+++ b/dom/apps/src/InterAppComm.manifest
@@ -0,0 +1,18 @@
+component {9dbfa904-0718-11e3-8e77-0721a45514b8} InterAppConnection.js
+contract @mozilla.org/dom/inter-app-connection;1 {9dbfa904-0718-11e3-8e77-0721a45514b8}
+
+component {6a77e9e0-0645-11e3-b90b-73bb7c78e06a} InterAppConnection.js
+contract @mozilla.org/dom/inter-app-connection-request;1 {6a77e9e0-0645-11e3-b90b-73bb7c78e06a}
+
+component {c66e0f8c-e3cb-11e2-9e85-43ef6244b884} InterAppMessagePort.js
+contract @mozilla.org/dom/inter-app-message-port;1 {c66e0f8c-e3cb-11e2-9e85-43ef6244b884}
+
+component {3dd15ce6-e7be-11e2-82bc-77967e7a63e6} InterAppCommService.js
+contract @mozilla.org/inter-app-communication-service;1 {3dd15ce6-e7be-11e2-82bc-77967e7a63e6}
+category profile-after-change InterAppCommService @mozilla.org/inter-app-communication-service;1
+
+component {d7c7a466-f91d-11e2-812a-6fab12ece58e} InterAppConnection.js
+contract @mozilla.org/dom/system-messages/wrapper/connection;1 {d7c7a466-f91d-11e2-812a-6fab12ece58e}
+
+component {33b4dff4-edf8-11e2-ae9c-77f99f99c3ad} InterAppMessagePort.js
+contract @mozilla.org/dom/inter-app-message-event;1 {33b4dff4-edf8-11e2-ae9c-77f99f99c3ad}
new file mode 100644
--- /dev/null
+++ b/dom/apps/src/InterAppCommService.js
@@ -0,0 +1,879 @@
+/* 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/. */
+
+"use strict";
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/AppsUtils.jsm");
+
+const DEBUG = false;
+function debug(aMsg) {
+  dump("-- InterAppCommService: " + Date.now() + ": " + aMsg + "\n");
+}
+
+XPCOMUtils.defineLazyServiceGetter(this, "appsService",
+                                   "@mozilla.org/AppsService;1",
+                                   "nsIAppsService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
+                                   "@mozilla.org/parentprocessmessagemanager;1",
+                                   "nsIMessageBroadcaster");
+
+XPCOMUtils.defineLazyServiceGetter(this, "UUIDGenerator",
+                                   "@mozilla.org/uuid-generator;1",
+                                   "nsIUUIDGenerator");
+
+XPCOMUtils.defineLazyServiceGetter(this, "messenger",
+                                   "@mozilla.org/system-message-internal;1",
+                                   "nsISystemMessagesInternal");
+
+const kMessages =["Webapps:Connect",
+                  "Webapps:GetConnections",
+                  "InterAppConnection:Cancel",
+                  "InterAppMessagePort:PostMessage",
+                  "InterAppMessagePort:Register",
+                  "InterAppMessagePort:Unregister",
+                  "child-process-shutdown"];
+
+function InterAppCommService() {
+  Services.obs.addObserver(this, "xpcom-shutdown", false);
+  Services.obs.addObserver(this, "inter-app-comm-select-app-result", false);
+
+  kMessages.forEach(function(aMsg) {
+    ppmm.addMessageListener(aMsg, this);
+  }, this);
+
+  // This matrix is used for saving the inter-app connection info registered in
+  // the app manifest. The object literal is defined as below:
+  //
+  // {
+  //   "keyword1": {
+  //     "subAppManifestURL1": {
+  //       /* subscribed info */
+  //     },
+  //     "subAppManifestURL2": {
+  //       /* subscribed info */
+  //     },
+  //     ...
+  //   },
+  //   "keyword2": {
+  //     "subAppManifestURL3": {
+  //       /* subscribed info */
+  //     },
+  //     ...
+  //   },
+  //   ...
+  // }
+  //
+  // For example:
+  //
+  // {
+  //   "foo": {
+  //     "app://subApp1.gaiamobile.org/manifest.webapp": {
+  //       pageURL: "app://subApp1.gaiamobile.org/handler.html",
+  //       description: "blah blah",
+  //       appStatus: Ci.nsIPrincipal.APP_STATUS_CERTIFIED,
+  //       rules: { ... }
+  //     },
+  //     "app://subApp2.gaiamobile.org/manifest.webapp": {
+  //       pageURL: "app://subApp2.gaiamobile.org/handler.html",
+  //       description: "blah blah",
+  //       appStatus: Ci.nsIPrincipal.APP_STATUS_PRIVILEGED,
+  //       rules: { ... }
+  //     }
+  //   },
+  //   "bar": {
+  //     "app://subApp3.gaiamobile.org/manifest.webapp": {
+  //       pageURL: "app://subApp3.gaiamobile.org/handler.html",
+  //       description: "blah blah",
+  //       appStatus: Ci.nsIPrincipal.APP_STATUS_INSTALLED,
+  //       rules: { ... }
+  //     }
+  //   }
+  // }
+  //
+  // TODO Bug 908999 - Update registered connections when app gets uninstalled.
+  this._registeredConnections = {};
+
+  // This matrix is used for saving the permitted connections, which allows
+  // the messaging between publishers and subscribers. The object literal is
+  // defined as below:
+  //
+  // {
+  //   "keyword1": {
+  //     "pubAppManifestURL1": [
+  //       "subAppManifestURL1",
+  //       "subAppManifestURL2",
+  //       ...
+  //     ],
+  //     "pubAppManifestURL2": [
+  //       "subAppManifestURL3",
+  //       "subAppManifestURL4",
+  //       ...
+  //     ],
+  //     ...
+  //   },
+  //   "keyword2": {
+  //     "pubAppManifestURL3": [
+  //       "subAppManifestURL5",
+  //       ...
+  //     ],
+  //     ...
+  //   },
+  //   ...
+  // }
+  //
+  // For example:
+  //
+  // {
+  //   "foo": {
+  //     "app://pubApp1.gaiamobile.org/manifest.webapp": [
+  //       "app://subApp1.gaiamobile.org/manifest.webapp",
+  //       "app://subApp2.gaiamobile.org/manifest.webapp"
+  //     ],
+  //     "app://pubApp2.gaiamobile.org/manifest.webapp": [
+  //       "app://subApp3.gaiamobile.org/manifest.webapp",
+  //       "app://subApp4.gaiamobile.org/manifest.webapp"
+  //     ]
+  //   },
+  //   "bar": {
+  //     "app://pubApp3.gaiamobile.org/manifest.webapp": [
+  //       "app://subApp5.gaiamobile.org/manifest.webapp",
+  //     ]
+  //   }
+  // }
+  //
+  // TODO Bug 908999 - Update allowed connections when app gets uninstalled.
+  this._allowedConnections = {};
+
+  // This matrix is used for saving the caller info from the content process,
+  // which is indexed by a random UUID, to know where to return the promise
+  // resolvser's callback when the prompt UI for allowing connections returns.
+  // An example of the object literal is shown as below:
+  //
+  // {
+  //   "fooID": {
+  //     outerWindowID: 12,
+  //     requestID: 34,
+  //     target: pubAppTarget1
+  //   },
+  //   "barID": {
+  //     outerWindowID: 56,
+  //     requestID: 78,
+  //     target: pubAppTarget2
+  //   }
+  // }
+  //
+  // where |outerWindowID| is the ID of the window requesting the connection,
+  //       |requestID| is the ID specifying the promise resolver to return,
+  //       |target| is the target of the process requesting the connection.
+  this._promptUICallers = {};
+
+  // This matrix is used for saving the pair of message ports, which is indexed
+  // by a random UUID, so that each port can know whom it should talk to.
+  // An example of the object literal is shown as below:
+  //
+  // {
+  //   "UUID1": {
+  //     keyword: "keyword1",
+  //     publisher: {
+  //       manifestURL: "app://pubApp1.gaiamobile.org/manifest.webapp",
+  //       target: pubAppTarget1,
+  //       pageURL: "app://pubApp1.gaiamobile.org/caller.html",
+  //       messageQueue: [...]
+  //     },
+  //     subscriber: {
+  //       manifestURL: "app://subApp1.gaiamobile.org/manifest.webapp",
+  //       target: subAppTarget1,
+  //       pageURL: "app://pubApp1.gaiamobile.org/handler.html",
+  //       messageQueue: [...]
+  //     }
+  //   },
+  //   "UUID2": {
+  //     keyword: "keyword2",
+  //     publisher: {
+  //       manifestURL: "app://pubApp2.gaiamobile.org/manifest.webapp",
+  //       target: pubAppTarget2,
+  //       pageURL: "app://pubApp2.gaiamobile.org/caller.html",
+  //       messageQueue: [...]
+  //     },
+  //     subscriber: {
+  //       manifestURL: "app://subApp2.gaiamobile.org/manifest.webapp",
+  //       target: subAppTarget2,
+  //       pageURL: "app://pubApp2.gaiamobile.org/handler.html",
+  //       messageQueue: [...]
+  //     }
+  //   }
+  // }
+  this._messagePortPairs = {};
+}
+
+InterAppCommService.prototype = {
+  registerConnection: function(aKeyword, aHandlerPageURI, aManifestURI,
+                               aDescription, aAppStatus, aRules) {
+    let manifestURL = aManifestURI.spec;
+    let pageURL = aHandlerPageURI.spec;
+
+    if (DEBUG) {
+      debug("registerConnection: aKeyword: " + aKeyword +
+            " manifestURL: " + manifestURL + " pageURL: " + pageURL +
+            " aDescription: " + aDescription + " aAppStatus: " + aAppStatus +
+            " aRules.minimumAccessLevel: " + aRules.minimumAccessLevel +
+            " aRules.manifestURLs: " + aRules.manifestURLs +
+            " aRules.installOrigins: " + aRules.installOrigins);
+    }
+
+    let subAppManifestURLs = this._registeredConnections[aKeyword];
+    if (!subAppManifestURLs) {
+      subAppManifestURLs = this._registeredConnections[aKeyword] = {};
+    }
+
+    subAppManifestURLs[manifestURL] = {
+      pageURL: pageURL,
+      description: aDescription,
+      appStatus: aAppStatus,
+      rules: aRules,
+      manifestURL: manifestURL
+    };
+  },
+
+  _matchMinimumAccessLevel: function(aRules, aAppStatus) {
+    if (!aRules || !aRules.minimumAccessLevel) {
+      if (DEBUG) {
+        debug("rules.minimumAccessLevel is not available. No need to match.");
+      }
+      return true;
+    }
+
+    let minAccessLevel = aRules.minimumAccessLevel;
+    switch (minAccessLevel) {
+      case "web":
+        if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_INSTALLED ||
+            aAppStatus == Ci.nsIPrincipal.APP_STATUS_PRIVILEGED ||
+            aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
+          return true;
+        }
+        break;
+      case "privileged":
+        if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_PRIVILEGED ||
+            aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
+          return true;
+        }
+        break;
+      case "certified":
+        if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
+          return true;
+        }
+        break;
+    }
+
+    if (DEBUG) {
+      debug("rules.minimumAccessLevel is not matched!" +
+            " minAccessLevel: " + minAccessLevel +
+            " aAppStatus : " + aAppStatus);
+    }
+    return false;
+  },
+
+  _matchManifestURLs: function(aRules, aManifestURL) {
+    if (!aRules || !Array.isArray(aRules.manifestURLs)) {
+      if (DEBUG) {
+        debug("rules.manifestURLs is not available. No need to match.");
+      }
+      return true;
+    }
+
+    let manifestURLs = aRules.manifestURLs;
+    if (manifestURLs.indexOf(aManifestURL) != -1) {
+      return true;
+    }
+
+    if (DEBUG) {
+      debug("rules.manifestURLs is not matched!" +
+            " manifestURLs: " + manifestURLs +
+            " aManifestURL : " + aManifestURL);
+    }
+    return false;
+  },
+
+  _matchInstallOrigins: function(aRules, aManifestURL) {
+    if (!aRules || !Array.isArray(aRules.installOrigins)) {
+      if (DEBUG) {
+        debug("rules.installOrigins is not available. No need to match.");
+      }
+      return true;
+    }
+
+    let installOrigin =
+      appsService.getAppByManifestURL(aManifestURL).installOrigin;
+
+    let installOrigins = aRules.installOrigins;
+    if (installOrigins.indexOf(installOrigin) != -1) {
+      return true;
+    }
+
+    if (DEBUG) {
+      debug("rules.installOrigins is not matched!" +
+            " aManifestURL: " + aManifestURL +
+            " installOrigins: " + installOrigins +
+            " installOrigin : " + installOrigin);
+    }
+    return false;
+  },
+
+  _matchRules: function(aPubAppManifestURL, aPubAppStatus, aPubRules,
+                        aSubAppManifestURL, aSubAppStatus, aSubRules) {
+    // TODO Bug 907068 In the initiative step, we only expose this API to
+    // certified apps to meet the time line. Eventually, we need to make
+    // it available for the non-certified apps as well. For now, only the
+    // certified apps can match the rules.
+    if (aPubAppStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED ||
+        aSubAppStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
+      if (DEBUG) {
+        debug("Only certified apps are allowed to do connections.");
+      }
+      return false;
+    }
+
+    if (!aPubRules && !aSubRules) {
+      if (DEBUG) {
+        debug("No rules for publisher and subscriber. No need to match.");
+      }
+      return true;
+    }
+
+    // Check minimumAccessLevel.
+    if (!this._matchMinimumAccessLevel(aPubRules, aSubAppStatus) ||
+        !this._matchMinimumAccessLevel(aSubRules, aPubAppStatus)) {
+      return false;
+    }
+
+    // Check manifestURLs.
+    if (!this._matchManifestURLs(aPubRules, aSubAppManifestURL) ||
+        !this._matchManifestURLs(aSubRules, aPubAppManifestURL)) {
+      return false;
+    }
+
+    // Check installOrigins.
+    if (!this._matchInstallOrigins(aPubRules, aSubAppManifestURL) ||
+        !this._matchInstallOrigins(aSubRules, aPubAppManifestURL)) {
+      return false;
+    }
+
+    // Check developers.
+    // TODO Do we really want to check this? This one seems naive.
+
+    if (DEBUG) debug("All rules are matched.");
+    return true;
+  },
+
+  _dispatchMessagePorts: function(aKeyword, aPubAppManifestURL,
+                                  aAllowedSubAppManifestURLs,
+                                  aTarget, aOuterWindowID, aRequestID) {
+    if (DEBUG) {
+      debug("_dispatchMessagePorts: aKeyword: " + aKeyword +
+            " aPubAppManifestURL: " + aPubAppManifestURL +
+            " aAllowedSubAppManifestURLs: " + aAllowedSubAppManifestURLs);
+    }
+
+    if (aAllowedSubAppManifestURLs.length == 0) {
+      if (DEBUG) debug("No apps are allowed to connect. Returning.");
+      aTarget.sendAsyncMessage("Webapps:Connect:Return:KO",
+                               { oid: aOuterWindowID, requestID: aRequestID });
+      return;
+    }
+
+    let subAppManifestURLs = this._registeredConnections[aKeyword];
+    if (!subAppManifestURLs) {
+      if (DEBUG) debug("No apps are subscribed to connect. Returning.");
+      aTarget.sendAsyncMessage("Webapps:Connect:Return:KO",
+                               { oid: aOuterWindowID, requestID: aRequestID });
+      return;
+    }
+
+    let messagePortIDs = [];
+    aAllowedSubAppManifestURLs.forEach(function(aAllowedSubAppManifestURL) {
+      let subscribedInfo = subAppManifestURLs[aAllowedSubAppManifestURL];
+      if (!subscribedInfo) {
+        if (DEBUG) {
+          debug("The sunscribed info is not available. Skipping: " +
+                aAllowedSubAppManifestURL);
+        }
+        return;
+      }
+
+      // The message port ID is aimed for identifying the coupling targets
+      // to deliver messages with each other. This ID is centrally generated
+      // by the parent and dispatched to both the sender and receiver ends
+      // for creating their own message ports respectively.
+      let messagePortID = UUIDGenerator.generateUUID().toString();
+      this._messagePortPairs[messagePortID] = {
+        keyword: aKeyword,
+        publisher: {
+          manifestURL: aPubAppManifestURL
+        },
+        subscriber: {
+          manifestURL: aAllowedSubAppManifestURL
+        }
+      };
+
+      // Fire system message to deliver the message port to the subscriber.
+      messenger.sendMessage("connection",
+        { keyword: aKeyword,
+          messagePortID: messagePortID },
+        Services.io.newURI(subscribedInfo.pageURL, null, null),
+        Services.io.newURI(subscribedInfo.manifestURL, null, null));
+
+      messagePortIDs.push(messagePortID);
+    }, this);
+
+    if (messagePortIDs.length == 0) {
+      if (DEBUG) debug("No apps are subscribed to connect. Returning.");
+      aTarget.sendAsyncMessage("Webapps:Connect:Return:KO",
+                               { oid: aOuterWindowID, requestID: aRequestID });
+      return;
+    }
+
+    // Return the message port IDs to open the message ports for the publisher.
+    if (DEBUG) debug("messagePortIDs: " + messagePortIDs);
+    aTarget.sendAsyncMessage("Webapps:Connect:Return:OK",
+                             { keyword: aKeyword,
+                               messagePortIDs: messagePortIDs,
+                               oid: aOuterWindowID, requestID: aRequestID });
+  },
+
+  _connect: function(aMessage, aTarget) {
+    let keyword = aMessage.keyword;
+    let pubRules = aMessage.rules;
+    let pubAppManifestURL = aMessage.manifestURL;
+    let outerWindowID = aMessage.outerWindowID;
+    let requestID = aMessage.requestID;
+    let pubAppStatus = aMessage.appStatus;
+
+    let subAppManifestURLs = this._registeredConnections[keyword];
+    if (!subAppManifestURLs) {
+      if (DEBUG) {
+        debug("No apps are subscribed for this connection. Returning.");
+      }
+      this._dispatchMessagePorts(keyword, pubAppManifestURL, [],
+                                 aTarget, outerWindowID, requestID);
+      return;
+    }
+
+    // Fetch the apps that used to be allowed to connect before, so that
+    // users don't need to select/allow them again. That is, we only pop up
+    // the prompt UI for the *new* connections.
+    let allowedSubAppManifestURLs = [];
+    let allowedPubAppManifestURLs = this._allowedConnections[keyword];
+    if (allowedPubAppManifestURLs &&
+        allowedPubAppManifestURLs[pubAppManifestURL]) {
+      allowedSubAppManifestURLs = allowedPubAppManifestURLs[pubAppManifestURL];
+    }
+
+    // Check rules to see if a subscribed app is allowed to connect.
+    let appsToSelect = [];
+    for (let subAppManifestURL in subAppManifestURLs) {
+      if (allowedSubAppManifestURLs.indexOf(subAppManifestURL) != -1) {
+        if (DEBUG) {
+          debug("Don't need to select again. Skipping: " + subAppManifestURL);
+        }
+        continue;
+      }
+
+      // Only rule-matched publishers/subscribers are allowed to connect.
+      let subscribedInfo = subAppManifestURLs[subAppManifestURL];
+      let subAppStatus = subscribedInfo.appStatus;
+      let subRules = subscribedInfo.rules;
+
+      let matched =
+        this._matchRules(pubAppManifestURL, pubAppStatus, pubRules,
+                         subAppManifestURL, subAppStatus, subRules);
+      if (!matched) {
+        if (DEBUG) {
+          debug("Rules are not matched. Skipping: " + subAppManifestURL);
+        }
+        continue;
+      }
+
+      appsToSelect.push({
+        manifestURL: subAppManifestURL,
+        description: subscribedInfo.description
+      });
+    }
+
+    if (appsToSelect.length == 0) {
+      if (DEBUG) {
+        debug("No additional apps need to be selected for this connection. " +
+              "Just dispatch message ports for the existing connections.");
+      }
+
+      this._dispatchMessagePorts(keyword, pubAppManifestURL,
+                                 allowedSubAppManifestURLs,
+                                 aTarget, outerWindowID, requestID);
+      return;
+    }
+
+    // Remember the caller info with an UUID so that we can know where to
+    // return the promise resolver's callback when the prompt UI returns.
+    let callerID = UUIDGenerator.generateUUID().toString();
+    this._promptUICallers[callerID] = {
+      outerWindowID: outerWindowID,
+      requestID: requestID,
+      target: aTarget
+    };
+
+    // TODO Bug 897169 Temporarily disable the notification for popping up
+    // the prompt until the UX/UI for the prompt is confirmed.
+    //
+    // TODO Bug 908191 We need to change the way of interaction between API and
+    // run-time prompt from observer notification to xpcom-interface caller.
+    //
+    /*
+    if (DEBUG) debug("appsToSelect: " + appsToSelect);
+    Services.obs.notifyObservers(null, "inter-app-comm-select-app",
+      JSON.stringify({ callerID: callerID,
+                       manifestURL: pubAppManifestURL,
+                       keyword: keyword,
+                       appsToSelect: appsToSelect }));
+    */
+
+    // TODO Bug 897169 Simulate the return of the app-selected result by
+    // the prompt, which always allows the connection. This dummy codes
+    // will be removed when the UX/UI for the prompt is ready.
+    if (DEBUG) debug("appsToSelect: " + appsToSelect);
+    Services.obs.notifyObservers(null, 'inter-app-comm-select-app-result',
+      JSON.stringify({ callerID: callerID,
+                       manifestURL: pubAppManifestURL,
+                       keyword: keyword,
+                       selectedApps: appsToSelect }));
+  },
+
+  _getConnections: function(aMessage, aTarget) {
+    let outerWindowID = aMessage.outerWindowID;
+    let requestID = aMessage.requestID;
+
+    let connections = [];
+    for (let keyword in this._allowedConnections) {
+      let allowedPubAppManifestURLs = this._allowedConnections[keyword];
+      for (let allowedPubAppManifestURL in allowedPubAppManifestURLs) {
+        let allowedSubAppManifestURLs =
+          allowedPubAppManifestURLs[allowedPubAppManifestURL];
+        allowedSubAppManifestURLs.forEach(function(allowedSubAppManifestURL) {
+          connections.push({ keyword: keyword,
+                             pubAppManifestURL: allowedPubAppManifestURL,
+                             subAppManifestURL: allowedSubAppManifestURL });
+        });
+      }
+    }
+
+    aTarget.sendAsyncMessage("Webapps:GetConnections:Return:OK",
+                             { connections: connections,
+                               oid: outerWindowID, requestID: requestID });
+  },
+
+  _cancelConnection: function(aMessage) {
+    let keyword = aMessage.keyword;
+    let pubAppManifestURL = aMessage.pubAppManifestURL;
+    let subAppManifestURL = aMessage.subAppManifestURL;
+
+    let allowedPubAppManifestURLs = this._allowedConnections[keyword];
+    if (!allowedPubAppManifestURLs) {
+      if (DEBUG) debug("keyword is not found: " + keyword);
+      return;
+    }
+
+    let allowedSubAppManifestURLs =
+      allowedPubAppManifestURLs[pubAppManifestURL];
+    if (!allowedSubAppManifestURLs) {
+      if (DEBUG) debug("publisher is not found: " + pubAppManifestURL);
+      return;
+    }
+
+    let index = allowedSubAppManifestURLs.indexOf(subAppManifestURL);
+    if (index == -1) {
+      if (DEBUG) debug("subscriber is not found: " + subAppManifestURL);
+      return;
+    }
+
+    if (DEBUG) debug("Cancelling the connection.");
+    allowedSubAppManifestURLs.splice(index, 1);
+
+    // Clean up the parent entries if needed.
+    if (allowedSubAppManifestURLs.length == 0) {
+      delete allowedPubAppManifestURLs[pubAppManifestURL];
+      if (Object.keys(allowedPubAppManifestURLs).length == 0) {
+        delete this._allowedConnections[keyword];
+      }
+    }
+
+    if (DEBUG) debug("Unregistering message ports based on this connection.");
+    let messagePortIDs = [];
+    for (let messagePortID in this._messagePortPairs) {
+      let pair = this._messagePortPairs[messagePortID];
+      if (pair.keyword == keyword &&
+          pair.publisher.manifestURL == pubAppManifestURL &&
+          pair.subscriber.manifestURL == subAppManifestURL) {
+        messagePortIDs.push(messagePortID);
+      }
+    }
+    messagePortIDs.forEach(function(aMessagePortID) {
+      delete this._messagePortPairs[aMessagePortID];
+    }, this);
+  },
+
+  _identifyMessagePort: function(aMessagePortID, aManifestURL) {
+    let pair = this._messagePortPairs[aMessagePortID];
+    if (!pair) {
+      if (DEBUG) {
+        debug("Error! The message port ID is invalid: " + aMessagePortID +
+              ", which should have been generated by parent.");
+      }
+      return null;
+    }
+
+    // Check it the message port is for publisher.
+    if (pair.publisher.manifestURL == aManifestURL) {
+      return { pair: pair, isPublisher: true };
+    }
+
+    // Check it the message port is for subscriber.
+    if (pair.subscriber.manifestURL == aManifestURL) {
+      return { pair: pair, isPublisher: false };
+    }
+
+    if (DEBUG) {
+      debug("Error! The manifest URL is invalid: " + aManifestURL +
+            ", which might be a hacked app.");
+    }
+    return null;
+  },
+
+  _registerMessagePort: function(aMessage, aTarget) {
+    let messagePortID = aMessage.messagePortID;
+    let manifestURL = aMessage.manifestURL;
+    let pageURL = aMessage.pageURL;
+
+    let identity = this._identifyMessagePort(messagePortID, manifestURL);
+    if (!identity) {
+      if (DEBUG) {
+        debug("Cannot identify the message port. Failed to register.");
+      }
+      return;
+    }
+
+    if (DEBUG) debug("Registering message port for " + manifestURL);
+    let pair = identity.pair;
+    let isPublisher = identity.isPublisher;
+
+    let sender = isPublisher ? pair.publisher : pair.subscriber;
+    sender.target = aTarget;
+    sender.pageURL = pageURL;
+    sender.messageQueue = [];
+
+    // Check if the other port has queued messages. Deliver them if needed.
+    if (DEBUG) {
+      debug("Checking if the other port used to send messages but queued.");
+    }
+    let receiver = isPublisher ? pair.subscriber : pair.publisher;
+    if (receiver.messageQueue) {
+      while (receiver.messageQueue.length) {
+        let message = receiver.messageQueue.shift();
+        if (DEBUG) debug("Delivering message: " + JSON.stringify(message));
+        sender.target.sendAsyncMessage("InterAppMessagePort:OnMessage",
+                                       { message: message,
+                                         manifestURL: sender.manifestURL,
+                                         pageURL: sender.pageURL,
+                                         messagePortID: messagePortID });
+      }
+    }
+  },
+
+  _unregisterMessagePort: function(aMessage) {
+    let messagePortID = aMessage.messagePortID;
+    let manifestURL = aMessage.manifestURL;
+
+    let identity = this._identifyMessagePort(messagePortID, manifestURL);
+    if (!identity) {
+      if (DEBUG) {
+        debug("Cannot identify the message port. Failed to unregister.");
+      }
+      return;
+    }
+
+    if (DEBUG) {
+      debug("Unregistering message port for " + manifestURL);
+    }
+    delete this._messagePortPairs[messagePortID];
+  },
+
+  _removeTarget: function(aTarget) {
+    if (!aTarget) {
+      if (DEBUG) debug("Error! aTarget cannot be null/undefined in any way.");
+      return
+    }
+
+    if (DEBUG) debug("Unregistering message ports based on this target.");
+    let messagePortIDs = [];
+    for (let messagePortID in this._messagePortPairs) {
+      let pair = this._messagePortPairs[messagePortID];
+      if (pair.publisher.target === aTarget ||
+          pair.subscriber.target === aTarget) {
+        messagePortIDs.push(messagePortID);
+      }
+    }
+    messagePortIDs.forEach(function(aMessagePortID) {
+      delete this._messagePortPairs[aMessagePortID];
+    }, this);
+  },
+
+  _postMessage: function(aMessage) {
+    let messagePortID = aMessage.messagePortID;
+    let manifestURL = aMessage.manifestURL;
+    let message = aMessage.message;
+
+    let identity = this._identifyMessagePort(messagePortID, manifestURL);
+    if (!identity) {
+      if (DEBUG) debug("Cannot identify the message port. Failed to post.");
+      return;
+    }
+
+    let pair = identity.pair;
+    let isPublisher = identity.isPublisher;
+
+    let receiver = isPublisher ? pair.subscriber : pair.publisher;
+    if (!receiver.target) {
+      if (DEBUG) {
+        debug("The receiver's target is not ready yet. Queuing the message.");
+      }
+      let sender = isPublisher ? pair.publisher : pair.subscriber;
+      sender.messageQueue.push(message);
+      return;
+    }
+
+    if (DEBUG) debug("Delivering message: " + JSON.stringify(message));
+    receiver.target.sendAsyncMessage("InterAppMessagePort:OnMessage",
+                                     { manifestURL: receiver.manifestURL,
+                                       pageURL: receiver.pageURL,
+                                       messagePortID: messagePortID,
+                                       message: message });
+  },
+
+  _handleSelectcedApps: function(aData) {
+    let callerID = aData.callerID;
+    let caller = this._promptUICallers[callerID];
+    if (!caller) {
+      if (DEBUG) debug("Error! Cannot find the caller.");
+      return;
+    }
+
+    delete this._promptUICallers[callerID];
+
+    let outerWindowID = caller.outerWindowID;
+    let requestID = caller.requestID;
+    let target = caller.target;
+
+    let manifestURL = aData.manifestURL;
+    let keyword = aData.keyword;
+    let selectedApps = aData.selectedApps;
+
+    if (selectedApps.length == 0) {
+      if (DEBUG) debug("No apps are selected to connect.")
+      this._dispatchMessagePorts(keyword, manifestURL, [],
+                                 target, outerWindowID, requestID);
+      return;
+    }
+
+    // Find the entry of allowed connections to add the selected apps.
+    let allowedPubAppManifestURLs = this._allowedConnections[keyword];
+    if (!allowedPubAppManifestURLs) {
+      allowedPubAppManifestURLs = this._allowedConnections[keyword] = {};
+    }
+    let allowedSubAppManifestURLs = allowedPubAppManifestURLs[manifestURL];
+    if (!allowedSubAppManifestURLs) {
+      allowedSubAppManifestURLs = allowedPubAppManifestURLs[manifestURL] = [];
+    }
+
+    // Add the selected app into the existing set of allowed connections.
+    selectedApps.forEach(function(aSelectedApp) {
+      let allowedSubAppManifestURL = aSelectedApp.manifestURL;
+      if (allowedSubAppManifestURLs.indexOf(allowedSubAppManifestURL) == -1) {
+        allowedSubAppManifestURLs.push(allowedSubAppManifestURL);
+      }
+    });
+
+    // Finally, dispatch the message ports for the allowed connections,
+    // including the old connections and the newly selected connection.
+    this._dispatchMessagePorts(keyword, manifestURL, allowedSubAppManifestURLs,
+                               target, outerWindowID, requestID);
+  },
+
+  receiveMessage: function(aMessage) {
+    if (DEBUG) debug("receiveMessage: name: " + aMessage.name);
+    let message = aMessage.json;
+    let target = aMessage.target;
+
+    // To prevent the hacked child process from sending commands to parent
+    // to do illegal connections, we need to check its manifest URL.
+    if (aMessage.name !== "child-process-shutdown" &&
+        kMessages.indexOf(aMessage.name) != -1) {
+      if (!target.assertContainApp(message.manifestURL)) {
+        if (DEBUG) {
+          debug("Got message from a process carrying illegal manifest URL.");
+        }
+        return null;
+      }
+    }
+
+    switch (aMessage.name) {
+      case "Webapps:Connect":
+        this._connect(message, target);
+        break;
+      case "Webapps:GetConnections":
+        this._getConnections(message, target);
+        break;
+      case "InterAppConnection:Cancel":
+        this._cancelConnection(message);
+        break;
+      case "InterAppMessagePort:PostMessage":
+        this._postMessage(message);
+        break;
+      case "InterAppMessagePort:Register":
+        this._registerMessagePort(message, target);
+        break;
+      case "InterAppMessagePort:Unregister":
+        this._unregisterMessagePort(message);
+        break;
+      case "child-process-shutdown":
+        this._removeTarget(target);
+        break;
+    }
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    switch (aTopic) {
+      case "xpcom-shutdown":
+        Services.obs.removeObserver(this, "xpcom-shutdown");
+        Services.obs.removeObserver(this, "inter-app-comm-select-app-result");
+        kMessages.forEach(function(aMsg) {
+          ppmm.removeMessageListener(aMsg, this);
+        }, this);
+        ppmm = null;
+        break;
+      case "inter-app-comm-select-app-result":
+        if (DEBUG) debug("inter-app-comm-select-app-result: " + aData);
+        this._handleSelectcedApps(JSON.parse(aData));
+        break;
+    }
+  },
+
+  classID: Components.ID("{3dd15ce6-e7be-11e2-82bc-77967e7a63e6}"),
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterAppCommService,
+                                         Ci.nsIObserver])
+}
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InterAppCommService]);
new file mode 100644
--- /dev/null
+++ b/dom/apps/src/InterAppConnection.js
@@ -0,0 +1,144 @@
+/* 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/. */
+
+"use strict";
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
+                                   "@mozilla.org/childprocessmessagemanager;1",
+                                   "nsIMessageSender");
+
+XPCOMUtils.defineLazyServiceGetter(this, "appsService",
+                                   "@mozilla.org/AppsService;1",
+                                   "nsIAppsService");
+
+const DEBUG = false;
+function debug(aMsg) {
+  dump("-- InterAppConnection: " + Date.now() + ": " + aMsg + "\n");
+}
+
+/**
+ * MozInterAppConnection implementation.
+ */
+
+function InterAppConnection() {
+  if (DEBUG) debug("InterAppConnection()");
+  this.keyword = null;
+  this.publisher = null;
+  this.subscriber = null;
+};
+
+InterAppConnection.prototype = {
+  __proto__: DOMRequestIpcHelper.prototype,
+
+  classDescription: "MozInterAppConnection",
+
+  classID: Components.ID("{9dbfa904-0718-11e3-8e77-0721a45514b8}"),
+
+  contractID: "@mozilla.org/dom/inter-app-connection;1",
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
+                                         Ci.nsISupportsWeakReference]),
+
+  __init: function(aKeyword, aPublisher, aSubscriber) {
+    if (DEBUG) {
+      debug("__init: aKeyword: " + aKeyword +
+            " aPublisher: " + aPublisher + " aSubscriber: " + aSubscriber);
+    }
+    this.keyword = aKeyword;
+    this.publisher = aPublisher;
+    this.subscriber = aSubscriber;
+  },
+
+  // Ci.nsIDOMGlobalPropertyInitializer implementation.
+  init: function(aWindow) {
+    if (DEBUG) debug("init");
+
+    this.initDOMRequestHelper(aWindow, []);
+    let principal = aWindow.document.nodePrincipal;
+    this._manifestURL = appsService.getManifestURLByLocalId(principal.appId);
+  },
+
+  cancel: function() {
+    if (DEBUG) debug("cancel");
+
+    cpmm.sendAsyncMessage("InterAppConnection:Cancel",
+                          { keyword: this.keyword,
+                            pubAppManifestURL: this.publisher,
+                            subAppManifestURL: this.subscriber,
+                            manifestURL: this._manifestURL });
+  }
+};
+
+
+/**
+ * MozInterAppConnectionRequest implementation.
+ */
+
+function InterAppConnectionRequest() {
+  if (DEBUG) debug("InterAppConnectionRequest()");
+  this.keyword = null;
+  this.port = null;
+};
+
+InterAppConnectionRequest.prototype = {
+  classDescription: "MozInterAppConnectionRequest",
+
+  classID: Components.ID("{6a77e9e0-0645-11e3-b90b-73bb7c78e06a}"),
+
+  contractID: "@mozilla.org/dom/inter-app-connection-request;1",
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
+
+  __init: function(aKeyword, aPort) {
+    if (DEBUG) debug("__init: aKeyword: " + aKeyword + " aPort: " + aPort);
+    this.keyword = aKeyword;
+    this.port = aPort;
+  }
+};
+
+/**
+ * InterAppConnectionRequestWrapper implementation.
+ *
+ * This implements nsISystemMessagesWrapper.wrapMessage(), which provides a
+ * plugable way to wrap a "connection" type system message.
+ *
+ * Please see SystemMessageManager.js to know how it customizes the wrapper.
+ */
+
+function InterAppConnectionRequestWrapper() {
+  if (DEBUG) debug("InterAppConnectionRequestWrapper()");
+}
+
+InterAppConnectionRequestWrapper.prototype = {
+  // nsISystemMessagesWrapper implementation.
+  wrapMessage: function(aMessage, aWindow) {
+    if (DEBUG) debug("wrapMessage: " + JSON.stringify(aMessage));
+
+    let port = new aWindow.MozInterAppMessagePort(aMessage.messagePortID);
+    let connectionRequest =
+      new aWindow.MozInterAppConnectionRequest(aMessage.keyword, port);
+
+    return connectionRequest;
+  },
+
+  classDescription: "InterAppConnectionRequestWrapper",
+
+  classID: Components.ID("{d7c7a466-f91d-11e2-812a-6fab12ece58e}"),
+
+  contractID: "@mozilla.org/dom/system-messages/wrapper/connection;1",
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISystemMessagesWrapper])
+}
+
+
+this.NSGetFactory =
+  XPCOMUtils.generateNSGetFactory([InterAppConnection,
+                                   InterAppConnectionRequest,
+                                   InterAppConnectionRequestWrapper]);
new file mode 100644
--- /dev/null
+++ b/dom/apps/src/InterAppMessagePort.js
@@ -0,0 +1,245 @@
+/* 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/. */
+
+// TODO Bug 907060 Per off-line discussion, after the MessagePort is done
+// at Bug 643325, we will start to refactorize the common logic of both
+// Inter-App Communication and Shared Worker. For now, we hope to design an
+// MozInterAppMessagePort to meet the timeline, which still follows exactly
+// the same interface and semantic as the MessagePort is. In the future,
+// we can then align it back to MessagePort with backward compatibility.
+
+"use strict";
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
+Cu.import("resource://gre/modules/ObjectWrapper.jsm");
+
+const DEBUG = false;
+function debug(aMsg) {
+  dump("-- InterAppMessagePort: " + Date.now() + ": " + aMsg + "\n");
+}
+
+XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
+                                   "@mozilla.org/childprocessmessagemanager;1",
+                                   "nsIMessageSender");
+
+XPCOMUtils.defineLazyServiceGetter(this, "appsService",
+                                   "@mozilla.org/AppsService;1",
+                                   "nsIAppsService");
+
+const kMessages = ["InterAppMessagePort:OnMessage"];
+
+
+function InterAppMessageEvent() {
+  this.type = this.data = null;
+};
+
+InterAppMessageEvent.prototype = {
+  classDescription: "MozInterAppMessageEvent",
+
+  classID: Components.ID("{33b4dff4-edf8-11e2-ae9c-77f99f99c3ad}"),
+
+  contractID: "@mozilla.org/dom/inter-app-message-event;1",
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
+
+  __init: function(aType, aDict) {
+    this.type = aType;
+    this.__DOM_IMPL__.initEvent(aType, aDict.bubbles || false,
+                                aDict.cancelable || false);
+    this.data = aDict.data;
+  }
+};
+
+
+function InterAppMessagePort() {
+  if (DEBUG) debug("InterAppMessagePort()");
+};
+
+InterAppMessagePort.prototype = {
+  __proto__: DOMRequestIpcHelper.prototype,
+
+  classDescription: "MozInterAppMessagePort",
+
+  classID: Components.ID("{c66e0f8c-e3cb-11e2-9e85-43ef6244b884}"),
+
+  contractID: "@mozilla.org/dom/inter-app-message-port;1",
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
+                                         Ci.nsISupportsWeakReference]),
+
+  // Ci.nsIDOMGlobalPropertyInitializer implementation.
+  init: function(aWindow) {
+    if (DEBUG) debug("Calling init().");
+
+    this.initDOMRequestHelper(aWindow, kMessages);
+
+    let principal = aWindow.document.nodePrincipal;
+    this._manifestURL = appsService.getManifestURLByLocalId(principal.appId);
+    this._pageURL = principal.URI.spec;
+
+    this._started = false;
+    this._closed = false;
+    this._messageQueue = [];
+  },
+
+  // WebIDL implementation for constructor.
+  __init: function(aMessagePortID) {
+    if (DEBUG) {
+      debug("Calling __init(): aMessagePortID: " + aMessagePortID);
+    }
+
+    this._messagePortID = aMessagePortID;
+
+    cpmm.sendAsyncMessage("InterAppMessagePort:Register",
+                          { messagePortID: this._messagePortID,
+                            manifestURL: this._manifestURL,
+                            pageURL: this._pageURL });
+  },
+
+  // DOMRequestIpcHelper implementation.
+  uninit: function() {
+    if (DEBUG) debug("Calling uninit().");
+
+    // When the message port is uninitialized, we need to disentangle the
+    // coupling ports, as if the close() method had been called.
+    if (this._closed) {
+      if (DEBUG) debug("close() has been called. Don't need to close again.");
+      return;
+    }
+
+    this.close();
+  },
+
+  postMessage: function(aMessage) {
+    if (DEBUG) debug("Calling postMessage().");
+
+    if (this._closed) {
+      if (DEBUG) debug("close() has been called. Cannot post message.");
+      return;
+    }
+
+    cpmm.sendAsyncMessage("InterAppMessagePort:PostMessage",
+                          { messagePortID: this._messagePortID,
+                            manifestURL: this._manifestURL,
+                            message: aMessage });
+  },
+
+  start: function() {
+    // Begin dispatching messages received on the port.
+    if (DEBUG) debug("Calling start().");
+
+    if (this._closed) {
+      if (DEBUG) debug("close() has been called. Cannot call start().");
+      return;
+    }
+
+    if (this._started) {
+      if (DEBUG) debug("start() has been called. Don't need to start again.");
+      return;
+    }
+
+    // When a port's port message queue is enabled, the event loop must use it
+    // as one of its task sources.
+    this._started = true;
+    while (this._messageQueue.length) {
+      let message = this._messageQueue.shift();
+      this._dispatchMessage(message);
+    }
+  },
+
+  close: function() {
+    // Disconnecting the port, so that it is no longer active.
+    if (DEBUG) debug("Calling close().");
+
+    if (this._closed) {
+      if (DEBUG) debug("close() has been called. Don't need to close again.");
+      return;
+    }
+
+    this._closed = true;
+    this._messageQueue.length = 0;
+
+    // When this method called on a local port that is entangled with another
+    // port, must cause the user agent to disentangle the coupling ports.
+    cpmm.sendAsyncMessage("InterAppMessagePort:Unregister",
+                          { messagePortID: this._messagePortID,
+                            manifestURL: this._manifestURL });
+  },
+
+  get onmessage() {
+    if (DEBUG) debug("Getting onmessage handler.");
+
+    return this.__DOM_IMPL__.getEventHandler("onmessage");
+  },
+
+  set onmessage(aHandler) {
+    if (DEBUG) debug("Setting onmessage handler.");
+
+    this.__DOM_IMPL__.setEventHandler("onmessage", aHandler);
+
+    // The first time a MessagePort object's onmessage IDL attribute is set,
+    // the port's message queue must be enabled, as if the start() method had
+    // been called.
+    if (this._started) {
+      if (DEBUG) debug("start() has been called. Don't need to start again.");
+      return;
+    }
+
+    this.start();
+  },
+
+  _dispatchMessage: function _dispatchMessage(aMessage) {
+    let wrappedMessage = ObjectWrapper.wrap(aMessage, this._window);
+    if (DEBUG) {
+      debug("_dispatchMessage: wrappedMessage: " +
+            JSON.stringify(wrappedMessage));
+    }
+
+    let event = new this._window
+                    .MozInterAppMessageEvent("message",
+                                             { data: wrappedMessage });
+    this.__DOM_IMPL__.dispatchEvent(event);
+  },
+
+  receiveMessage: function(aMessage) {
+    if (DEBUG) debug("receiveMessage: name: " + aMessage.name);
+
+    let message = aMessage.json;
+    if (message.manifestURL != this._manifestURL ||
+        message.pageURL != this._pageURL ||
+        message.messagePortID != this._messagePortID) {
+      if (DEBUG) debug("The message doesn't belong to this page. Returning.");
+      return;
+    }
+
+    switch (aMessage.name) {
+      case "InterAppMessagePort:OnMessage":
+        if (this._closed) {
+          if (DEBUG) debug("close() has been called. Drop the message.");
+          return;
+        }
+
+        if (!this._started) {
+          if (DEBUG) debug("Not yet called start(). Queue up the message.");
+          this._messageQueue.push(message.message);
+          return;
+        }
+
+        this._dispatchMessage(message.message);
+        break;
+
+      default:
+        if (DEBUG) debug("Error! Shouldn't fall into this case.");
+        break;
+    }
+  }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InterAppMessagePort,
+                                                     InterAppMessageEvent]);
+
new file mode 100644
--- /dev/null
+++ b/dom/apps/src/Makefile.in
@@ -0,0 +1,10 @@
+# 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/.
+
+LOCAL_INCLUDES += \
+  -I$(topsrcdir)/js/xpconnect/wrappers \
+  $(NULL)
+
+include $(topsrcdir)/dom/dom-config.mk
+include $(topsrcdir)/config/rules.mk
--- a/dom/apps/src/Webapps.js
+++ b/dom/apps/src/Webapps.js
@@ -304,16 +304,18 @@ function WebappsApplication() {
   this.wrappedJSObject = this;
 }
 
 WebappsApplication.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
 
   init: function(aWindow, aApp) {
     this._window = aWindow;
+    let principal = this._window.document.nodePrincipal;
+    this._appStatus = principal.appStatus;
     this.origin = aApp.origin;
     this._manifest = aApp.manifest;
     this._updateManifest = aApp.updateManifest;
     this.manifestURL = aApp.manifestURL;
     this.receipts = aApp.receipts;
     this.installOrigin = aApp.installOrigin;
     this.installTime = aApp.installTime;
     this.installState = aApp.installState || "installed";
@@ -337,17 +339,20 @@ WebappsApplication.prototype = {
     this._downloadError = null;
 
     this.initDOMRequestHelper(aWindow, ["Webapps:OfflineCache",
                               "Webapps:CheckForUpdate:Return:OK",
                               "Webapps:CheckForUpdate:Return:KO",
                               "Webapps:Launch:Return:OK",
                               "Webapps:Launch:Return:KO",
                               "Webapps:PackageEvent",
-                              "Webapps:ClearBrowserData:Return"]);
+                              "Webapps:ClearBrowserData:Return",
+                              "Webapps:Connect:Return:OK",
+                              "Webapps:Connect:Return:KO",
+                              "Webapps:GetConnections:Return:OK"]);
 
     cpmm.sendAsyncMessage("Webapps:RegisterForMessages",
                           ["Webapps:OfflineCache",
                            "Webapps:PackageEvent",
                            "Webapps:CheckForUpdate:Return:OK"]);
   },
 
   get manifest() {
@@ -455,16 +460,43 @@ WebappsApplication.prototype = {
         }
       }
       Services.tm.currentThread.dispatch(runnable,
                                          Ci.nsIThread.DISPATCH_NORMAL);
     }
     return request;
   },
 
+  connect: function(aKeyword, aRules) {
+    return this.createPromise(function (aResolve, aReject) {
+      cpmm.sendAsyncMessage("Webapps:Connect",
+                            { keyword: aKeyword,
+                              rules: aRules,
+                              manifestURL: this.manifestURL,
+                              outerWindowID: this._id,
+                              appStatus: this._appStatus,
+                              requestID: this.getPromiseResolverId({
+                                resolve: aResolve,
+                                reject: aReject
+                              })});
+    }.bind(this));
+  },
+
+  getConnections: function() {
+    return this.createPromise(function (aResolve, aReject) {
+      cpmm.sendAsyncMessage("Webapps:GetConnections",
+                            { manifestURL: this.manifestURL,
+                              outerWindowID: this._id,
+                              requestID: this.getPromiseResolverId({
+                                resolve: aResolve,
+                                reject: aReject
+                              })});
+    }.bind(this));
+  },
+
   uninit: function() {
     this._onprogress = null;
     cpmm.sendAsyncMessage("Webapps:UnregisterForMessages",
                           ["Webapps:OfflineCache",
                            "Webapps:PackageEvent",
                            "Webapps:CheckForUpdate:Return:OK"]);
 
     manifestCache.evict(this.manifestURL, this.innerWindowID);
@@ -474,17 +506,24 @@ WebappsApplication.prototype = {
     if (aHandler) {
       let event = new this._window.MozApplicationEvent(aName, { application: this });
       aHandler.handleEvent(event);
     }
   },
 
   receiveMessage: function(aMessage) {
     let msg = aMessage.json;
-    let req = this.takeRequest(msg.requestID);
+    let req;
+    if (aMessage.name == "Webapps:Connect:Return:OK" ||
+        aMessage.name == "Webapps:Connect:Return:KO" ||
+        aMessage.name == "Webapps:GetConnections:Return:OK") {
+      req = this.takePromiseResolver(msg.requestID);
+    } else {
+      req = this.takeRequest(msg.requestID);
+    }
 
     // ondownload* callbacks should be triggered on all app instances
     if ((msg.oid != this._id || !req) &&
         aMessage.name !== "Webapps:OfflineCache" &&
         aMessage.name !== "Webapps:PackageEvent" &&
         aMessage.name !== "Webapps:CheckForUpdate:Return:OK")
       return;
     switch (aMessage.name) {
@@ -598,16 +637,38 @@ WebappsApplication.prototype = {
             this._manifest = msg.manifest;
             this._fireEvent("downloadapplied", this._ondownloadapplied);
             break;
         }
         break;
       case "Webapps:ClearBrowserData:Return":
         Services.DOMRequest.fireSuccess(req, null);
         break;
+      case "Webapps:Connect:Return:OK":
+        let messagePorts = [];
+        msg.messagePortIDs.forEach(function(aPortID) {
+          let port = new this._window.MozInterAppMessagePort(aPortID);
+          messagePorts.push(port);
+        }, this);
+        req.resolve(messagePorts);
+        break;
+      case "Webapps:Connect:Return:KO":
+        req.reject("No connections registered");
+        break;
+      case "Webapps:GetConnections:Return:OK":
+        let connections = [];
+        msg.connections.forEach(function(aConnection) {
+          let connection =
+            new this._window.MozInterAppConnection(aConnection.keyword,
+                                                   aConnection.pubAppManifestURL,
+                                                   aConnection.subAppManifestURL);
+          connections.push(connection);
+        }, this);
+        req.resolve(connections);
+        break;
     }
   },
 
   classID: Components.ID("{723ed303-7757-4fb0-b261-4f78b1f6bd22}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMApplication,
                                          Ci.nsISupportsWeakReference]),
 
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -55,16 +55,21 @@ XPCOMUtils.defineLazyGetter(this, "NetUt
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                    "@mozilla.org/parentprocessmessagemanager;1",
                                    "nsIMessageBroadcaster");
 
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsIMessageSender");
 
+XPCOMUtils.defineLazyGetter(this, "interAppCommService", function() {
+  return Cc["@mozilla.org/inter-app-communication-service;1"]
+         .getService(Ci.nsIInterAppCommService);
+});
+
 XPCOMUtils.defineLazyGetter(this, "msgmgr", function() {
   return Cc["@mozilla.org/system-message-internal;1"]
          .getService(Ci.nsISystemMessagesInternal);
 });
 
 XPCOMUtils.defineLazyGetter(this, "updateSvc", function() {
   return Cc["@mozilla.org/offlinecacheupdate-service;1"]
            .getService(Ci.nsIOfflineCacheUpdateService);
@@ -525,16 +530,18 @@ this.DOMApplicationRegistry = {
 #else
       onAppsLoaded();
 #endif
     }).bind(this));
   },
 
   // |aEntryPoint| is either the entry_point name or the null in which case we
   // use the root of the manifest.
+  //
+  // TODO Bug 908094 Refine _registerSystemMessagesForEntryPoint(...).
   _registerSystemMessagesForEntryPoint: function(aManifest, aApp, aEntryPoint) {
     let root = aManifest;
     if (aEntryPoint && aManifest.entry_points[aEntryPoint]) {
       root = aManifest.entry_points[aEntryPoint];
     }
 
     if (!root.messages || !Array.isArray(root.messages) ||
         root.messages.length == 0) {
@@ -566,28 +573,104 @@ this.DOMApplicationRegistry = {
             .isSystemMessagePermittedToRegister(messageName,
                                                 aApp.origin,
                                                 aManifest)) {
         msgmgr.registerPage(messageName, href, manifestURL);
       }
     });
   },
 
+  // |aEntryPoint| is either the entry_point name or the null in which case we
+  // use the root of the manifest.
+  //
+  // TODO Bug 908094 Refine _registerInterAppConnectionsForEntryPoint(...).
+  _registerInterAppConnectionsForEntryPoint: function(aManifest, aApp,
+                                                      aEntryPoint) {
+    let root = aManifest;
+    if (aEntryPoint && aManifest.entry_points[aEntryPoint]) {
+      root = aManifest.entry_points[aEntryPoint];
+    }
+
+    let connections = root.connections;
+    if (!connections) {
+      return;
+    }
+
+    if ((typeof connections) !== "object") {
+      debug("|connections| is not an object. Skipping: " + connections);
+      return;
+    }
+
+    let manifest = new ManifestHelper(aManifest, aApp.origin);
+    let launchPathURI = Services.io.newURI(manifest.fullLaunchPath(aEntryPoint),
+                                           null, null);
+    let manifestURI = Services.io.newURI(aApp.manifestURL, null, null);
+
+    for (let keyword in connections) {
+      let connection = connections[keyword];
+
+      // Resolve the handler path from origin. If |handler_path| is absent,
+      // use |launch_path| as default.
+      let fullHandlerPath;
+      let handlerPath = connection.handler_path;
+      if (handlerPath) {
+        try {
+          fullHandlerPath = manifest.resolveFromOrigin(handlerPath);
+        } catch(e) {
+          debug("Connection's handler path is invalid. Skipping: keyword: " +
+                keyword + " handler_path: " + handlerPath);
+          continue;
+        }
+      }
+      let handlerPageURI = fullHandlerPath
+                           ? Services.io.newURI(fullHandlerPath, null, null)
+                           : launchPathURI;
+
+      if (SystemMessagePermissionsChecker
+            .isSystemMessagePermittedToRegister("connection",
+                                                aApp.origin,
+                                                aManifest)) {
+        msgmgr.registerPage("connection", handlerPageURI, manifestURI);
+      }
+
+      interAppCommService.
+        registerConnection(keyword,
+                           handlerPageURI,
+                           manifestURI,
+                           connection.description,
+                           AppsUtils.getAppManifestStatus(manifest),
+                           connection.rules);
+    }
+  },
+
   _registerSystemMessages: function(aManifest, aApp) {
     this._registerSystemMessagesForEntryPoint(aManifest, aApp, null);
 
     if (!aManifest.entry_points) {
       return;
     }
 
     for (let entryPoint in aManifest.entry_points) {
       this._registerSystemMessagesForEntryPoint(aManifest, aApp, entryPoint);
     }
   },
 
+  _registerInterAppConnections: function(aManifest, aApp) {
+    this._registerInterAppConnectionsForEntryPoint(aManifest, aApp, null);
+
+    if (!aManifest.entry_points) {
+      return;
+    }
+
+    for (let entryPoint in aManifest.entry_points) {
+      this._registerInterAppConnectionsForEntryPoint(aManifest, aApp,
+                                                     entryPoint);
+    }
+  },
+
   // |aEntryPoint| is either the entry_point name or the null in which case we
   // use the root of the manifest.
   _createActivitiesToRegister: function(aManifest, aApp, aEntryPoint, aRunUpdate) {
     let activitiesToRegister = [];
     let root = aManifest;
     if (aEntryPoint && aManifest.entry_points[aEntryPoint]) {
       root = aManifest.entry_points[aEntryPoint];
     }
@@ -740,16 +823,17 @@ this.DOMApplicationRegistry = {
         let manifest = aResult.manifest;
         app.name = manifest.name;
         app.csp = manifest.csp || "";
         app.role = manifest.role || "";
         if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
           app.redirects = this.sanitizeRedirects(manifest.redirects);
         }
         this._registerSystemMessages(manifest, app);
+        this._registerInterAppConnections(manifest, app);
         appsToRegister.push({ manifest: manifest, app: app });
       }, this);
       this._registerActivitiesForApps(appsToRegister, aRunUpdate);
     }).bind(this));
   },
 
   observe: function(aSubject, aTopic, aData) {
     if (aTopic == "xpcom-shutdown") {
@@ -1391,16 +1475,17 @@ this.DOMApplicationRegistry = {
     }
 
     if (supportSystemMessages()) {
       if (aOldManifest) {
         this._unregisterActivities(aOldManifest, aApp);
       }
       this._registerSystemMessages(aNewManifest, aApp);
       this._registerActivities(aNewManifest, aApp, true);
+      this._registerInterAppConnections(aNewManifest, aApp);
     } else {
       // Nothing else to do but notifying we're ready.
       this.notifyAppsRegistryReady();
     }
   },
 
   checkForUpdate: function(aData, aMm) {
     debug("checkForUpdate for " + aData.manifestURL);
--- a/dom/apps/src/moz.build
+++ b/dom/apps/src/moz.build
@@ -1,17 +1,29 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+EXPORTS.mozilla.dom += [
+    'InterAppComm.h',
+]
+
+CPP_SOURCES += [
+    'InterAppComm.cpp',
+]
+
 EXTRA_COMPONENTS += [
     'AppsService.js',
     'AppsService.manifest',
+    'InterAppComm.manifest',
+    'InterAppCommService.js',
+    'InterAppConnection.js',
+    'InterAppMessagePort.js',
     'Webapps.js',
     'Webapps.manifest',
 ]
 
 EXTRA_JS_MODULES += [
     'AppDownloadManager.jsm',
     'AppsServiceChild.jsm',
     'FreeSpaceWatcher.jsm',
@@ -20,8 +32,13 @@ EXTRA_JS_MODULES += [
     'PermissionsTable.jsm',
 ]
 
 EXTRA_PP_JS_MODULES += [
     'AppsUtils.jsm',
     'Webapps.jsm',
 ]
 
+FAIL_ON_WARNINGS = True
+
+LIBXUL_LIBRARY = True
+
+LIBRARY_NAME = 'dom_apps_s'
new file mode 100644
--- /dev/null
+++ b/dom/base/CompositionStringSynthesizer.cpp
@@ -0,0 +1,157 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CompositionStringSynthesizer.h"
+#include "nsContentUtils.h"
+#include "nsIDocShell.h"
+#include "nsIFrame.h"
+#include "nsIPresShell.h"
+#include "nsIWidget.h"
+#include "nsPIDOMWindow.h"
+#include "nsView.h"
+
+namespac