diff --git a/browser/components/sessionstore/content/content-sessionStore.js b/browser/components/sessionstore/content/content-sessionStore.js index 4fb77aa4722..9b592daabc3 100644 --- a/browser/components/sessionstore/content/content-sessionStore.js +++ b/browser/components/sessionstore/content/content-sessionStore.js @@ -244,20 +244,34 @@ let ScrollPositionListener = { * 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]) }; diff --git a/browser/components/sessionstore/src/PageStyle.jsm b/browser/components/sessionstore/src/PageStyle.jsm index 0907d24242d..c677adc5dc8 100644 --- a/browser/components/sessionstore/src/PageStyle.jsm +++ b/browser/components/sessionstore/src/PageStyle.jsm @@ -12,13 +12,17 @@ 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. @@ -26,47 +30,29 @@ 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; }, /** @@ -91,4 +77,51 @@ let PageStyleInternal = { }); } }, + + /** + * 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); + } }; diff --git a/browser/components/sessionstore/src/SessionStore.jsm b/browser/components/sessionstore/src/SessionStore.jsm index b12f0d2c4d9..ebea9743661 100644 --- a/browser/components/sessionstore/src/SessionStore.jsm +++ b/browser/components/sessionstore/src/SessionStore.jsm @@ -2976,11 +2976,17 @@ let SessionStoreInternal = { } 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; diff --git a/browser/components/sessionstore/test/browser_pageStyle.js b/browser/components/sessionstore/test/browser_pageStyle.js index 155cd47ce0f..7a3a9b19963 100644 --- a/browser/components/sessionstore/test/browser_pageStyle.js +++ b/browser/components/sessionstore/test/browser_pageStyle.js @@ -61,7 +61,8 @@ add_task(function nested_page_style() { 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) { diff --git a/browser/components/sessionstore/test/browser_pageStyle_sample.html b/browser/components/sessionstore/test/browser_pageStyle_sample.html index dd2758f0089..810054049b3 100644 --- a/browser/components/sessionstore/test/browser_pageStyle_sample.html +++ b/browser/components/sessionstore/test/browser_pageStyle_sample.html @@ -11,9 +11,6 @@ - - -