Bug 950132 - Fix pageStyle data collection r=yoric
authorTim Taubert <ttaubert@mozilla.com>
Thu, 19 Dec 2013 12:21:51 +0100
changeset 161355 9f97b68c6bdf00bfcfd246be4bd8d5d9985da36a
parent 161354 0ecaf7b5bc926a7c12e84e37187f83fe766ba151
child 161356 2898275c1c43545f04d54e8843fc302cc4e9148a
push id25878
push userkwierso@gmail.com
push dateFri, 20 Dec 2013 03:09:21 +0000
treeherdermozilla-central@599100c4ebfe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersyoric
bugs950132
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 950132 - Fix pageStyle data collection r=yoric From af5873fe7e4f55dd72f0e62d5c22285c2c94e40d Mon Sep 17 00:00:00 2001
browser/components/sessionstore/content/content-sessionStore.js
browser/components/sessionstore/src/PageStyle.jsm
browser/components/sessionstore/src/SessionStore.jsm
browser/components/sessionstore/test/browser_pageStyle.js
browser/components/sessionstore/test/browser_pageStyle_sample.html
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -239,30 +239,44 @@ let ScrollPositionListener = {
 };
 
 /**
  * Listens for changes to the page style. Whenever a different page style is
  * selected or author styles are enabled/disabled we send a message with the
  * currently applied style to the chrome process.
  *
  * Causes a SessionStore:update message to be sent that contains the currently
- * selected pageStyle, if any. The pageStyle is represented by a string.
+ * selected pageStyle for all reachable frames.
+ *
+ * Example:
+ *   {pageStyle: "Dusk", children: [null, {pageStyle: "Mozilla"}]}
  */
 let PageStyleListener = {
   init: function () {
     Services.obs.addObserver(this, "author-style-disabled-changed", true);
     Services.obs.addObserver(this, "style-sheet-applicable-state-changed", true);
+    gFrameTree.addObserver(this);
   },
 
   observe: function (subject, topic) {
-    if (subject.defaultView && subject.defaultView.top == content) {
-      MessageQueue.push("pageStyle", () => PageStyle.collect(docShell) || null);
+    let frame = subject.defaultView;
+
+    if (frame && gFrameTree.contains(frame)) {
+      MessageQueue.push("pageStyle", () => this.collect());
     }
   },
 
+  collect: function () {
+    return PageStyle.collect(docShell, gFrameTree);
+  },
+
+  onFrameTreeReset: function () {
+    MessageQueue.push("pageStyle", () => null);
+  },
+
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference])
 };
 
 /**
  * Listens for changes to docShell capabilities. Whenever a new load is started
  * we need to re-check the list of capabilities and send message when it has
  * changed.
--- a/browser/components/sessionstore/src/PageStyle.jsm
+++ b/browser/components/sessionstore/src/PageStyle.jsm
@@ -7,71 +7,57 @@
 this.EXPORTED_SYMBOLS = ["PageStyle"];
 
 const Ci = Components.interfaces;
 
 /**
  * The external API exported by this module.
  */
 this.PageStyle = Object.freeze({
-  collect: function (docShell) {
-    return PageStyleInternal.collect(docShell);
+  collect: function (docShell, frameTree) {
+    return PageStyleInternal.collect(docShell, frameTree);
   },
 
   restore: function (docShell, frameList, pageStyle) {
     PageStyleInternal.restore(docShell, frameList, pageStyle);
   },
+
+  restoreTree: function (docShell, data) {
+    PageStyleInternal.restoreTree(docShell, data);
+  }
 });
 
 // Signifies that author style level is disabled for the page.
 const NO_STYLE = "_nostyle";
 
 let PageStyleInternal = {
   /**
-   * Find out the title of the style sheet selected for the given
-   * docshell. Recurse into frames if needed.
+   * Collects the selected style sheet sets for all reachable frames.
    */
-  collect: function (docShell) {
+  collect: function (docShell, frameTree) {
+    let result = frameTree.map(({document: doc}) => {
+      let style;
+
+      if (doc) {
+        // http://dev.w3.org/csswg/cssom/#persisting-the-selected-css-style-sheet-set
+        style = doc.selectedStyleSheetSet || doc.lastStyleSheetSet;
+      }
+
+      return style ? {pageStyle: style} : null;
+    });
+
     let markupDocumentViewer =
       docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer);
+
     if (markupDocumentViewer.authorStyleDisabled) {
-      return NO_STYLE;
+      result = result || {};
+      result.disabled = true;
     }
 
-    let content = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
-
-    return this.collectFrame(content);
-  },
-
-  /**
-   * Determine the title of the currently enabled style sheet (if any);
-   * recurse through the frameset if necessary.
-   * @param   content is a frame reference
-   * @returns the title style sheet determined to be enabled (empty string if none)
-   */
-  collectFrame: function (content) {
-    const forScreen = /(?:^|,)\s*(?:all|screen)\s*(?:,|$)/i;
-
-    let sheets = content.document.styleSheets;
-    for (let i = 0; i < sheets.length; i++) {
-      let ss = sheets[i];
-      let media = ss.media.mediaText;
-      if (!ss.disabled && ss.title && (!media || forScreen.test(media))) {
-        return ss.title;
-      }
-    }
-
-    for (let i = 0; i < content.frames.length; i++) {
-      let selectedPageStyle = this.collectFrame(content.frames[i]);
-      if (selectedPageStyle) {
-        return selectedPageStyle;
-      }
-    }
-
-    return "";
+    return result && Object.keys(result).length ? result : null;
   },
 
   /**
    * Restore the selected style sheet of all the frames in frameList
    * to match |pageStyle|.
    * @param docShell the root docshell of all the frames
    * @param frameList a list of [frame, data] pairs, where frame is a
    * DOM window and data is the session restore data associated with
@@ -86,9 +72,56 @@ let PageStyleInternal = {
     markupDocumentViewer.authorStyleDisabled = disabled;
 
     for (let [frame, data] of frameList) {
       Array.forEach(frame.document.styleSheets, function(aSS) {
         aSS.disabled = aSS.title && aSS.title != pageStyle;
       });
     }
   },
+
+  /**
+   * Restores pageStyle data for the current frame hierarchy starting at the
+   * |docShell's| current DOMWindow using the given pageStyle |data|.
+   *
+   * Warning: If the current frame hierarchy doesn't match that of the given
+   * |data| object we will silently discard data for unreachable frames. We may
+   * as well assign page styles to the wrong frames if some were reordered or
+   * removed.
+   *
+   * @param docShell (nsIDocShell)
+   * @param data (object)
+   *        {
+   *          disabled: true, // when true, author styles will be disabled
+   *          pageStyle: "Dusk",
+   *          children: [
+   *            null,
+   *            {pageStyle: "Mozilla", children: [ ... ]}
+   *          ]
+   *        }
+   */
+  restoreTree: function (docShell, data) {
+    let disabled = data.disabled || false;
+    let markupDocumentViewer =
+      docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer);
+    markupDocumentViewer.authorStyleDisabled = disabled;
+
+    function restoreFrame(root, data) {
+      if (data.hasOwnProperty("pageStyle")) {
+        root.document.selectedStyleSheetSet = data.pageStyle;
+      }
+
+      if (!data.hasOwnProperty("children")) {
+        return;
+      }
+
+      let frames = root.frames;
+      data.children.forEach((child, index) => {
+        if (child && index < frames.length) {
+          restoreFrame(frames[index], child);
+        }
+      });
+    }
+
+    let ifreq = docShell.QueryInterface(Ci.nsIInterfaceRequestor);
+    restoreFrame(ifreq.getInterface(Ci.nsIDOMWindow), data);
+  }
 };
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -2971,21 +2971,27 @@ let SessionStoreInternal = {
   restoreDocument: function ssi_restoreDocument(aWindow, aBrowser, aEvent) {
     // wait for the top frame to be loaded completely
     if (!aEvent || !aEvent.originalTarget || !aEvent.originalTarget.defaultView ||
         aEvent.originalTarget.defaultView != aEvent.originalTarget.defaultView.top) {
       return;
     }
 
     let frameList = this.getFramesToRestore(aBrowser);
-    let pageStyle = RestoreData.get(aBrowser, "pageStyle") || "";
+    let pageStyle = RestoreData.get(aBrowser, "pageStyle") || {};
     let scrollPositions = RestoreData.get(aBrowser, "scroll") || {};
 
-    PageStyle.restore(aBrowser.docShell, frameList, pageStyle);
-    ScrollPosition.restoreTree(aBrowser.contentWindow, scrollPositions);
+    // Support the old pageStyle format.
+    if (typeof(pageStyle) === "string") {
+      PageStyle.restore(aBrowser.docShell, frameList, pageStyle);
+    } else {
+      ScrollPosition.restoreTree(aBrowser.contentWindow, scrollPositions);
+    }
+
+    PageStyle.restoreTree(aBrowser.docShell, pageStyle);
     TextAndScrollData.restore(frameList);
 
     let tab = aBrowser.__SS_restore_tab;
 
     // Drop all the state associated with restoring the tab. We're
     // done with that now.
     delete aBrowser.__SS_data;
     delete aBrowser.__SS_restore_data;
--- a/browser/components/sessionstore/test/browser_pageStyle.js
+++ b/browser/components/sessionstore/test/browser_pageStyle.js
@@ -56,17 +56,18 @@ add_task(function nested_page_style() {
   let tab = gBrowser.addTab(URL_NESTED);
   let browser = tab.linkedBrowser;
   yield promiseBrowserLoaded(browser);
 
   yield enableSubDocumentStyleSheetsForSet(browser, "alternate");
   gBrowser.removeTab(tab);
 
   let [{state: {pageStyle}}] = JSON.parse(ss.getClosedTabData(window));
-  is(pageStyle, "alternate", "correct pageStyle persisted");
+  let expected = JSON.stringify({children: [{pageStyle: "alternate"}]});
+  is(JSON.stringify(pageStyle), expected, "correct pageStyle persisted");
 });
 
 function getStyleSheets(browser) {
   return sendMessage(browser, "ss-test:getStyleSheets");
 }
 
 function enableStyleSheetsForSet(browser, name) {
   return sendMessage(browser, "ss-test:enableStyleSheetsForSet", name);
--- a/browser/components/sessionstore/test/browser_pageStyle_sample.html
+++ b/browser/components/sessionstore/test/browser_pageStyle_sample.html
@@ -6,14 +6,11 @@
   <link href="404.css" title="default" rel="stylesheet">
   <link href="404.css" title="alternate" rel="alternate stylesheet">
   <link href="404.css" title="altERnate" rel=" styLEsheet altERnate ">
   <link href="404.css" title="media_empty" rel="alternate stylesheet" media="">
   <link href="404.css" title="media_all" rel="alternate stylesheet" media="all">
   <link href="404.css" title="media_ALL" rel="alternate stylesheet" media=" ALL ">
   <link href="404.css" title="media_screen" rel="alternate stylesheet" media="screen">
   <link href="404.css" title="media_print_screen" rel="alternate stylesheet" media="print,screen">
-  <link href="404.css" title="fail_media_print" rel="alternate stylesheet" media="print">
-  <link href="404.css" title="fail_media_projection" rel="stylesheet" media="projection">
-  <link href="404.css" title="fail_media_invalid" rel="alternate stylesheet" media="hallo">
 </head>
 <body></body>
 </html>