mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 06:43:32 +00:00
Merge fx-team to m-c a=merge CLOSED TREE
This commit is contained in:
commit
ed7713496a
@ -10,15 +10,34 @@ XPCOMUtils.defineLazyModuleGetter(this, "ReadingList",
|
||||
const READINGLIST_COMMAND_ID = "readingListSidebar";
|
||||
|
||||
let ReadingListUI = {
|
||||
/**
|
||||
* Frame-script messages we want to listen to.
|
||||
* @type {[string]}
|
||||
*/
|
||||
MESSAGES: [
|
||||
"ReadingList:GetVisibility",
|
||||
"ReadingList:ToggleVisibility",
|
||||
],
|
||||
|
||||
/**
|
||||
* Add-to-ReadingList toolbar button in the URLbar.
|
||||
* @type {Element}
|
||||
*/
|
||||
toolbarButton: null,
|
||||
|
||||
/**
|
||||
* Whether this object is currently registered as a listener with ReadingList.
|
||||
* Used to avoid inadvertantly loading the ReadLingList.jsm module on startup.
|
||||
* @type {Boolean}
|
||||
*/
|
||||
listenerRegistered: false,
|
||||
|
||||
/**
|
||||
* Initialize the ReadingList UI.
|
||||
*/
|
||||
init() {
|
||||
this.toolbarButton = document.getElementById("readinglist-addremove-button");
|
||||
|
||||
Preferences.observe("browser.readinglist.enabled", this.updateUI, this);
|
||||
|
||||
const mm = window.messageManager;
|
||||
@ -63,7 +82,18 @@ let ReadingListUI = {
|
||||
*/
|
||||
updateUI() {
|
||||
let enabled = this.enabled;
|
||||
if (!enabled) {
|
||||
if (enabled) {
|
||||
// This is a no-op if we're already registered.
|
||||
ReadingList.addListener(this);
|
||||
this.listenerRegistered = true;
|
||||
} else {
|
||||
if (this.listenerRegistered) {
|
||||
// This is safe to call if we're not currently registered, but we don't
|
||||
// want to forcibly load the normally lazy-loaded module on startup.
|
||||
ReadingList.removeListener(this);
|
||||
this.listenerRegistered = true;
|
||||
}
|
||||
|
||||
this.hideSidebar();
|
||||
}
|
||||
|
||||
@ -89,6 +119,11 @@ let ReadingListUI = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Re-refresh the ReadingList bookmarks submenu when it opens.
|
||||
*
|
||||
* @param {Element} target - Menu element opening.
|
||||
*/
|
||||
onReadingListPopupShowing: Task.async(function* (target) {
|
||||
if (target.id == "BMB_readingListPopup") {
|
||||
// Setting this class in the .xul file messes with the way
|
||||
@ -184,4 +219,96 @@ let ReadingListUI = {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles toolbar button styling based on page proxy state changes.
|
||||
*
|
||||
* @see SetPageProxyState()
|
||||
*
|
||||
* @param {string} state - New state. Either "valid" or "invalid".
|
||||
*/
|
||||
onPageProxyStateChanged: Task.async(function* (state) {
|
||||
if (!this.toolbarButton) {
|
||||
// nothing to do if we have no button.
|
||||
return;
|
||||
}
|
||||
if (!this.enabled || state == "invalid") {
|
||||
this.toolbarButton.setAttribute("hidden", true);
|
||||
return;
|
||||
}
|
||||
|
||||
let isInList = yield ReadingList.containsURL(gBrowser.currentURI);
|
||||
this.setToolbarButtonState(isInList);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Set the state of the ReadingList toolbar button in the urlbar.
|
||||
* If the current tab's page is in the ReadingList (active), sets the button
|
||||
* to allow removing the page. Otherwise, sets the button to allow adding the
|
||||
* page (not active).
|
||||
*
|
||||
* @param {boolean} active - True if the button should be active (page is
|
||||
* already in the list).
|
||||
*/
|
||||
setToolbarButtonState(active) {
|
||||
this.toolbarButton.setAttribute("already-added", active);
|
||||
|
||||
let type = (active ? "remove" : "add");
|
||||
let tooltip = gNavigatorBundle.getString(`readingList.urlbar.${type}`);
|
||||
this.toolbarButton.setAttribute("tooltiptext", tooltip);
|
||||
|
||||
this.toolbarButton.removeAttribute("hidden");
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle a page (from a browser) in the ReadingList, adding if it's not already added, or
|
||||
* removing otherwise.
|
||||
*
|
||||
* @param {<xul:browser>} browser - Browser with page to toggle.
|
||||
* @returns {Promise} Promise resolved when operation has completed.
|
||||
*/
|
||||
togglePageByBrowser: Task.async(function* (browser) {
|
||||
let item = yield ReadingList.getItemForURL(browser.currentURI);
|
||||
if (item) {
|
||||
yield item.delete();
|
||||
} else {
|
||||
yield ReadingList.addItemFromBrowser(browser);
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Checks if a given item matches the current tab in this window.
|
||||
*
|
||||
* @param {ReadingListItem} item - Item to check
|
||||
* @returns True if match, false otherwise.
|
||||
*/
|
||||
isItemForCurrentBrowser(item) {
|
||||
let currentURL = gBrowser.currentURI.spec;
|
||||
if (item.url == currentURL || item.resolvedURL == currentURL) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* ReadingList event handler for when an item is added.
|
||||
*
|
||||
* @param {ReadingListItem} item - Item added.
|
||||
*/
|
||||
onItemAdded(item) {
|
||||
if (this.isItemForCurrentBrowser(item)) {
|
||||
this.setToolbarButtonState(true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* ReadingList event handler for when an item is deleted.
|
||||
*
|
||||
* @param {ReadingListItem} item - Item deleted.
|
||||
*/
|
||||
onItemDeleted(item) {
|
||||
if (this.isItemForCurrentBrowser(item)) {
|
||||
this.setToolbarButtonState(false);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -1022,6 +1022,7 @@ var gBrowserInit = {
|
||||
CombinedStopReload.init();
|
||||
gPrivateBrowsingUI.init();
|
||||
TabsInTitlebar.init();
|
||||
ReadingListUI.init();
|
||||
|
||||
#ifdef XP_WIN
|
||||
if (window.matchMedia("(-moz-os-version: windows-win8)").matches &&
|
||||
@ -1379,7 +1380,6 @@ var gBrowserInit = {
|
||||
|
||||
SocialUI.init();
|
||||
TabView.init();
|
||||
ReadingListUI.init();
|
||||
|
||||
// Telemetry for master-password - we do this after 5 seconds as it
|
||||
// can cause IO if NSS/PSM has not already initialized.
|
||||
@ -2421,6 +2421,7 @@ function UpdatePageProxyState()
|
||||
function SetPageProxyState(aState)
|
||||
{
|
||||
BookmarkingUI.onPageProxyStateChanged(aState);
|
||||
ReadingListUI.onPageProxyStateChanged(aState);
|
||||
|
||||
if (!gURLBar)
|
||||
return;
|
||||
|
@ -827,6 +827,10 @@
|
||||
hidden="true"
|
||||
tooltiptext="&pageReportIcon.tooltip;"
|
||||
onclick="gPopupBlockerObserver.onReportButtonClick(event);"/>
|
||||
<toolbarbutton id="readinglist-addremove-button"
|
||||
class="tabbable urlbar-icon"
|
||||
hidden="true"
|
||||
oncommand="ReadingListUI.togglePageByBrowser(gBrowser.selectedBrowser);"/>
|
||||
<toolbarbutton id="reader-mode-button"
|
||||
class="tabbable"
|
||||
hidden="true"
|
||||
|
@ -1017,12 +1017,12 @@ addEventListener("pageshow", function(event) {
|
||||
});
|
||||
|
||||
let PageMetadataMessenger = {
|
||||
init: function() {
|
||||
init() {
|
||||
addMessageListener("PageMetadata:GetPageData", this);
|
||||
addMessageListener("PageMetadata:GetMicrodata", this);
|
||||
},
|
||||
receiveMessage: function(aMessage) {
|
||||
switch(aMessage.name) {
|
||||
receiveMessage(message) {
|
||||
switch(message.name) {
|
||||
case "PageMetadata:GetPageData": {
|
||||
let result = PageMetadata.getData(content.document);
|
||||
sendAsyncMessage("PageMetadata:PageDataResult", result);
|
||||
@ -1030,7 +1030,7 @@ let PageMetadataMessenger = {
|
||||
}
|
||||
|
||||
case "PageMetadata:GetMicrodata": {
|
||||
let target = aMessage.objects;
|
||||
let target = message.objects;
|
||||
let result = PageMetadata.getMicrodata(content.document, target);
|
||||
sendAsyncMessage("PageMetadata:MicrodataResult", result);
|
||||
break;
|
||||
|
@ -1,12 +1,43 @@
|
||||
function test()
|
||||
{
|
||||
var embed = '<embed type="application/x-test" allowscriptaccess="always" allowfullscreen="true" wmode="window" width="640" height="480"></embed>'
|
||||
function swapTabsAndCloseOther(a, b) {
|
||||
gBrowser.swapBrowsersAndCloseOther(gBrowser.tabs[b], gBrowser.tabs[a]);
|
||||
}
|
||||
|
||||
waitForExplicitFinish();
|
||||
let getClicks = function(tab) {
|
||||
return ContentTask.spawn(tab.linkedBrowser, {}, function() {
|
||||
return content.wrappedJSObject.clicks;
|
||||
});
|
||||
}
|
||||
|
||||
let clickTest = Task.async(function*(tab) {
|
||||
let clicks = yield getClicks(tab);
|
||||
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function() {
|
||||
let target = content.document.body;
|
||||
let rect = target.getBoundingClientRect();
|
||||
let left = (rect.left + rect.right) / 2;
|
||||
let top = (rect.top + rect.bottom) / 2;
|
||||
|
||||
let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||
.getInterface(Components.interfaces.nsIDOMWindowUtils);
|
||||
utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
|
||||
utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
|
||||
});
|
||||
|
||||
let newClicks = yield getClicks(tab);
|
||||
is(newClicks, clicks + 1, "adding 1 more click on BODY");
|
||||
});
|
||||
|
||||
function loadURI(tab, url) {
|
||||
tab.linkedBrowser.loadURI(url);
|
||||
return BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
}
|
||||
|
||||
add_task(function*() {
|
||||
let embed = '<embed type="application/x-test" allowscriptaccess="always" allowfullscreen="true" wmode="window" width="640" height="480"></embed>'
|
||||
setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED);
|
||||
|
||||
// create a few tabs
|
||||
var tabs = [
|
||||
let tabs = [
|
||||
gBrowser.tabs[0],
|
||||
gBrowser.addTab("about:blank", {skipAnimation: true}),
|
||||
gBrowser.addTab("about:blank", {skipAnimation: true}),
|
||||
@ -14,118 +45,73 @@ function test()
|
||||
gBrowser.addTab("about:blank", {skipAnimation: true})
|
||||
];
|
||||
|
||||
function setLocation(i, url) {
|
||||
tabs[i].linkedBrowser.contentWindow.location = url;
|
||||
}
|
||||
function moveTabTo(a, b) {
|
||||
gBrowser.swapBrowsersAndCloseOther(gBrowser.tabs[b], gBrowser.tabs[a]);
|
||||
}
|
||||
function clickTest(tab, doc, win) {
|
||||
var clicks = doc.defaultView.clicks;
|
||||
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function() {
|
||||
let target = content.document.body;
|
||||
let rect = target.getBoundingClientRect();
|
||||
let left = (rect.left + rect.right) / 2;
|
||||
let top = (rect.top + rect.bottom) / 2;
|
||||
|
||||
let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||
.getInterface(Components.interfaces.nsIDOMWindowUtils);
|
||||
utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
|
||||
utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
|
||||
});
|
||||
|
||||
is(doc.defaultView.clicks, clicks+1, "adding 1 more click on BODY");
|
||||
}
|
||||
function test1() {
|
||||
moveTabTo(2, 3); // now: 0 1 2 4
|
||||
is(gBrowser.tabs[1], tabs[1], "tab1");
|
||||
is(gBrowser.tabs[2], tabs[3], "tab3");
|
||||
|
||||
var plugin = tabs[4].linkedBrowser.contentDocument.wrappedJSObject.body.firstChild;
|
||||
var tab4_plugin_object = plugin.getObjectValue();
|
||||
|
||||
gBrowser.selectedTab = gBrowser.tabs[2];
|
||||
moveTabTo(3, 2); // now: 0 1 4
|
||||
gBrowser.selectedTab = tabs[4];
|
||||
var doc = gBrowser.tabs[2].linkedBrowser.contentDocument.wrappedJSObject;
|
||||
plugin = doc.body.firstChild;
|
||||
ok(plugin && plugin.checkObjectValue(tab4_plugin_object), "same plugin instance");
|
||||
is(gBrowser.tabs[1], tabs[1], "tab1");
|
||||
is(gBrowser.tabs[2], tabs[3], "tab4");
|
||||
is(doc.defaultView.clicks, 0, "no click on BODY so far");
|
||||
clickTest(gBrowser.tabs[2], doc, window);
|
||||
|
||||
moveTabTo(2, 1); // now: 0 4
|
||||
is(gBrowser.tabs[1], tabs[1], "tab1");
|
||||
doc = gBrowser.tabs[1].linkedBrowser.contentDocument.wrappedJSObject;
|
||||
plugin = doc.body.firstChild;
|
||||
ok(plugin && plugin.checkObjectValue(tab4_plugin_object), "same plugin instance");
|
||||
clickTest(gBrowser.tabs[1], doc, window);
|
||||
|
||||
// Load a new document (about:blank) in tab4, then detach that tab into a new window.
|
||||
// In the new window, navigate back to the original document and click on its <body>,
|
||||
// verify that its onclick was called.
|
||||
var t = tabs[1];
|
||||
var b = t.linkedBrowser;
|
||||
gBrowser.selectedTab = t;
|
||||
b.addEventListener("load", function() {
|
||||
b.removeEventListener("load", arguments.callee, true);
|
||||
|
||||
executeSoon(function () {
|
||||
var win = gBrowser.replaceTabWithWindow(t);
|
||||
whenDelayedStartupFinished(win, function () {
|
||||
// Verify that the original window now only has the initial tab left in it.
|
||||
is(gBrowser.tabs[0], tabs[0], "tab0");
|
||||
is(gBrowser.tabs[0].linkedBrowser.contentWindow.location, "about:blank", "tab0 uri");
|
||||
|
||||
executeSoon(function () {
|
||||
win.gBrowser.addEventListener("pageshow", function () {
|
||||
win.gBrowser.removeEventListener("pageshow", arguments.callee, false);
|
||||
executeSoon(function () {
|
||||
t = win.gBrowser.tabs[0];
|
||||
b = t.linkedBrowser;
|
||||
var doc = b.contentDocument.wrappedJSObject;
|
||||
clickTest(t, doc, win);
|
||||
win.close();
|
||||
finish();
|
||||
});
|
||||
}, false);
|
||||
win.gBrowser.goBack();
|
||||
});
|
||||
});
|
||||
});
|
||||
}, true);
|
||||
b.loadURI("about:blank");
|
||||
|
||||
}
|
||||
|
||||
var loads = 0;
|
||||
function waitForLoad(event, tab, listenerContainer) {
|
||||
var b = tabs[tab].linkedBrowser;
|
||||
if (b.contentDocument != event.target) {
|
||||
return;
|
||||
}
|
||||
gBrowser.tabs[tab].linkedBrowser.removeEventListener("load", listenerContainer.listener, true);
|
||||
++loads;
|
||||
if (loads == tabs.length - 1) {
|
||||
executeSoon(test1);
|
||||
}
|
||||
}
|
||||
|
||||
function fn(f, arg) {
|
||||
var listenerContainer = { listener: null }
|
||||
listenerContainer.listener = function (event) { return f(event, arg, listenerContainer); };
|
||||
return listenerContainer.listener;
|
||||
}
|
||||
for (var i = 1; i < tabs.length; ++i) {
|
||||
tabs[i].linkedBrowser.addEventListener("load", fn(waitForLoad,i), true);
|
||||
}
|
||||
|
||||
setLocation(1, "data:text/html;charset=utf-8,<title>tab1</title><body>tab1<iframe>");
|
||||
setLocation(2, "data:text/plain;charset=utf-8,tab2");
|
||||
setLocation(3, "data:text/html;charset=utf-8,<title>tab3</title><body>tab3<iframe>");
|
||||
setLocation(4, "data:text/html;charset=utf-8,<body onload='clicks=0' onclick='++clicks'>"+embed);
|
||||
// Initially 0 1 2 3 4
|
||||
yield loadURI(tabs[1], "data:text/html;charset=utf-8,<title>tab1</title><body>tab1<iframe>");
|
||||
yield loadURI(tabs[2], "data:text/plain;charset=utf-8,tab2");
|
||||
yield loadURI(tabs[3], "data:text/html;charset=utf-8,<title>tab3</title><body>tab3<iframe>");
|
||||
yield loadURI(tabs[4], "data:text/html;charset=utf-8,<body onload='clicks=0' onclick='++clicks'>"+embed);
|
||||
gBrowser.selectedTab = tabs[3];
|
||||
|
||||
}
|
||||
swapTabsAndCloseOther(2, 3); // now: 0 1 2 4
|
||||
is(gBrowser.tabs[1], tabs[1], "tab1");
|
||||
is(gBrowser.tabs[2], tabs[3], "tab3");
|
||||
is(gBrowser.tabs[3], tabs[4], "tab4");
|
||||
|
||||
let plugin = tabs[4].linkedBrowser.contentDocument.wrappedJSObject.body.firstChild;
|
||||
let tab4_plugin_object = plugin.getObjectValue();
|
||||
|
||||
swapTabsAndCloseOther(3, 2); // now: 0 1 4
|
||||
gBrowser.selectedTab = gBrowser.tabs[2];
|
||||
|
||||
let doc = gBrowser.tabs[2].linkedBrowser.contentDocument.wrappedJSObject;
|
||||
plugin = doc.body.firstChild;
|
||||
ok(plugin && plugin.checkObjectValue(tab4_plugin_object), "same plugin instance");
|
||||
|
||||
is(gBrowser.tabs[1], tabs[1], "tab1");
|
||||
is(gBrowser.tabs[2], tabs[3], "tab4");
|
||||
|
||||
let clicks = yield getClicks(gBrowser.tabs[2]);
|
||||
is(clicks, 0, "no click on BODY so far");
|
||||
yield clickTest(gBrowser.tabs[2]);
|
||||
|
||||
swapTabsAndCloseOther(2, 1); // now: 0 4
|
||||
is(gBrowser.tabs[1], tabs[1], "tab1");
|
||||
|
||||
doc = gBrowser.tabs[1].linkedBrowser.contentDocument.wrappedJSObject;
|
||||
plugin = doc.body.firstChild;
|
||||
ok(plugin && plugin.checkObjectValue(tab4_plugin_object), "same plugin instance");
|
||||
|
||||
yield clickTest(gBrowser.tabs[1]);
|
||||
|
||||
// Load a new document (about:blank) in tab4, then detach that tab into a new window.
|
||||
// In the new window, navigate back to the original document and click on its <body>,
|
||||
// verify that its onclick was called.
|
||||
gBrowser.selectedTab = tabs[1];
|
||||
yield loadURI(tabs[1], "about:blank");
|
||||
let key = tabs[1].linkedBrowser.permanentKey;
|
||||
|
||||
let win = gBrowser.replaceTabWithWindow(tabs[1]);
|
||||
yield new Promise(resolve => whenDelayedStartupFinished(win, resolve));
|
||||
|
||||
// Verify that the original window now only has the initial tab left in it.
|
||||
is(gBrowser.tabs[0], tabs[0], "tab0");
|
||||
is(gBrowser.tabs[0].linkedBrowser.currentURI.spec, "about:blank", "tab0 uri");
|
||||
|
||||
let tab = win.gBrowser.tabs[0];
|
||||
is(tab.linkedBrowser.permanentKey, key, "Should have kept the key");
|
||||
|
||||
let pageshowPromise = ContentTask.spawn(tab.linkedBrowser, {}, function*() {
|
||||
return new Promise(resolve => {
|
||||
let listener = function() {
|
||||
removeEventListener("pageshow", listener, false);
|
||||
resolve();
|
||||
}
|
||||
addEventListener("pageshow", listener, false);
|
||||
});
|
||||
});
|
||||
win.gBrowser.goBack();
|
||||
yield pageshowPromise;
|
||||
|
||||
yield clickTest(tab);
|
||||
promiseWindowClosed(win);
|
||||
});
|
||||
|
@ -55,8 +55,7 @@ const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directory.source";
|
||||
* This test ensures that there are no unexpected
|
||||
* uninterruptible reflows when opening new tabs.
|
||||
*/
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
add_task(function*() {
|
||||
let DirectoryLinksProvider = Cu.import("resource:///modules/DirectoryLinksProvider.jsm", {}).DirectoryLinksProvider;
|
||||
let NewTabUtils = Cu.import("resource://gre/modules/NewTabUtils.jsm", {}).NewTabUtils;
|
||||
let Promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
|
||||
@ -85,25 +84,36 @@ function test() {
|
||||
return watchLinksChangeOnce();
|
||||
});
|
||||
|
||||
// run tests when directory source change completes
|
||||
watchLinksChangeOnce().then(() => {
|
||||
// Add a reflow observer and open a new tab.
|
||||
docShell.addWeakReflowObserver(observer);
|
||||
BrowserOpenTab();
|
||||
|
||||
// Wait until the tabopen animation has finished.
|
||||
waitForTransitionEnd(function () {
|
||||
// Remove reflow observer and clean up.
|
||||
docShell.removeWeakReflowObserver(observer);
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
});
|
||||
});
|
||||
|
||||
Services.prefs.setBoolPref(PREF_PRELOAD, false);
|
||||
// set directory source to dummy/empty links
|
||||
Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, 'data:application/json,{"test":1}');
|
||||
}
|
||||
|
||||
// run tests when directory source change completes
|
||||
yield watchLinksChangeOnce();
|
||||
|
||||
// Perform a click in the top left of content to ensure the mouse isn't
|
||||
// hovering over any of the tiles
|
||||
let target = gBrowser.selectedBrowser;
|
||||
let rect = target.getBoundingClientRect();
|
||||
let left = rect.left + 1;
|
||||
let top = rect.top + 1;
|
||||
|
||||
let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
|
||||
utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
|
||||
|
||||
// Add a reflow observer and open a new tab.
|
||||
docShell.addWeakReflowObserver(observer);
|
||||
BrowserOpenTab();
|
||||
|
||||
// Wait until the tabopen animation has finished.
|
||||
yield waitForTransitionEnd();
|
||||
|
||||
// Remove reflow observer and clean up.
|
||||
docShell.removeWeakReflowObserver(observer);
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
let observer = {
|
||||
reflow: function (start, end) {
|
||||
@ -137,12 +147,14 @@ let observer = {
|
||||
Ci.nsISupportsWeakReference])
|
||||
};
|
||||
|
||||
function waitForTransitionEnd(callback) {
|
||||
let tab = gBrowser.selectedTab;
|
||||
tab.addEventListener("transitionend", function onEnd(event) {
|
||||
if (event.propertyName === "max-width") {
|
||||
tab.removeEventListener("transitionend", onEnd);
|
||||
executeSoon(callback);
|
||||
}
|
||||
function waitForTransitionEnd() {
|
||||
return new Promise(resolve => {
|
||||
let tab = gBrowser.selectedTab;
|
||||
tab.addEventListener("transitionend", function onEnd(event) {
|
||||
if (event.propertyName === "max-width") {
|
||||
tab.removeEventListener("transitionend", onEnd);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -129,6 +129,38 @@ ReadingListImpl.prototype = {
|
||||
return (yield this._store.count(...optsList));
|
||||
}),
|
||||
|
||||
/**
|
||||
* Checks whether a given URL is in the ReadingList already.
|
||||
*
|
||||
* @param {String/nsIURI} url - URL to check.
|
||||
* @returns {Promise} Promise that is fulfilled with a boolean indicating
|
||||
* whether the URL is in the list or not.
|
||||
*/
|
||||
containsURL: Task.async(function* (url) {
|
||||
url = normalizeURI(url).spec;
|
||||
|
||||
// This is used on every tab switch and page load of the current tab, so we
|
||||
// want it to be quick and avoid a DB query whenever possible.
|
||||
|
||||
// First check if any cached items have a direct match.
|
||||
if (this._itemsByURL.has(url)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Then check if any cached items may have a different resolved URL
|
||||
// that matches.
|
||||
for (let itemWeakRef of this._itemsByURL.values()) {
|
||||
let item = itemWeakRef.get();
|
||||
if (item && item.resolvedURL == url) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, fall back to the DB.
|
||||
let count = yield this.count({url: url}, {resolvedURL: url});
|
||||
return (count > 0);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Enumerates the items in the list that match the given options.
|
||||
*
|
||||
@ -190,6 +222,7 @@ ReadingListImpl.prototype = {
|
||||
*/
|
||||
addItem: Task.async(function* (obj) {
|
||||
obj = stripNonItemProperties(obj);
|
||||
normalizeReadingListProperties(obj);
|
||||
yield this._store.addItem(obj);
|
||||
this._invalidateIterators();
|
||||
let item = this._itemFromObject(obj);
|
||||
@ -248,11 +281,38 @@ ReadingListImpl.prototype = {
|
||||
* @param {String/nsIURI} uri - URI to match against. This will be normalized.
|
||||
*/
|
||||
getItemForURL: Task.async(function* (uri) {
|
||||
let url = this._normalizeURI(uri).spec;
|
||||
let url = normalizeURI(uri).spec;
|
||||
let [item] = yield this.iterator({url: url}, {resolvedURL: url}).items(1);
|
||||
return item;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Add to the ReadingList the page that is loaded in a given browser.
|
||||
*
|
||||
* @param {<xul:browser>} browser - Browser element for the document.
|
||||
* @return {Promise} Promise that is fullfilled with the added item.
|
||||
*/
|
||||
addItemFromBrowser: Task.async(function* (browser) {
|
||||
let metadata = yield getMetadataFromBrowser(browser);
|
||||
let itemData = {
|
||||
url: browser.currentURI,
|
||||
title: metadata.title,
|
||||
resolvedURL: metadata.url,
|
||||
excerpt: metadata.description,
|
||||
};
|
||||
|
||||
if (metadata.description) {
|
||||
itemData.exerpt = metadata.description;
|
||||
}
|
||||
|
||||
if (metadata.previews.length > 0) {
|
||||
itemData.image = metadata.previews[0];
|
||||
}
|
||||
|
||||
let item = yield ReadingList.addItem(itemData);
|
||||
return item;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Adds a listener that will be notified when the list changes. Listeners
|
||||
* are objects with the following optional methods:
|
||||
@ -304,22 +364,6 @@ ReadingListImpl.prototype = {
|
||||
// A Set containing listener objects.
|
||||
_listeners: null,
|
||||
|
||||
/**
|
||||
* Normalize a URI, stripping away extraneous parts we don't want to store
|
||||
* or compare against.
|
||||
*
|
||||
* @param {nsIURI/String} uri - URI to normalize.
|
||||
* @returns {nsIURI} Cloned and normalized version of the input URI.
|
||||
*/
|
||||
_normalizeURI(uri) {
|
||||
if (typeof uri == "string") {
|
||||
uri = Services.io.newURI(uri, "", null);
|
||||
}
|
||||
uri = uri.cloneIgnoringRef();
|
||||
uri.userPass = "";
|
||||
return uri;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the ReadingListItem represented by the given simple object. If
|
||||
* the item doesn't exist yet, it's created first.
|
||||
@ -375,12 +419,26 @@ ReadingListImpl.prototype = {
|
||||
},
|
||||
|
||||
_ensureItemBelongsToList(item) {
|
||||
if (item.list != this) {
|
||||
throw new Error("The item does not belong to this list");
|
||||
if (!item || !item._ensureBelongsToList) {
|
||||
throw new Error("The item is not a ReadingListItem");
|
||||
}
|
||||
item._ensureBelongsToList();
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
* normalize the properties of a "regular" object that reflects a ReadingListItem
|
||||
*/
|
||||
function normalizeReadingListProperties(obj) {
|
||||
if (obj.url) {
|
||||
obj.url = normalizeURI(obj.url).spec;
|
||||
}
|
||||
if (obj.resolvedURL) {
|
||||
obj.resolvedURL = normalizeURI(obj.resolvedURL).spec;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let _unserializable = () => {}; // See comments in the ReadingListItem ctor.
|
||||
|
||||
/**
|
||||
@ -431,9 +489,6 @@ ReadingListItem.prototype = {
|
||||
},
|
||||
set guid(val) {
|
||||
this._properties.guid = val;
|
||||
if (this.list) {
|
||||
this.commit();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -447,9 +502,6 @@ ReadingListItem.prototype = {
|
||||
},
|
||||
set lastModified(val) {
|
||||
this._properties.lastModified = val.valueOf();
|
||||
if (this.list) {
|
||||
this.commit();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -460,10 +512,7 @@ ReadingListItem.prototype = {
|
||||
return this._properties.url;
|
||||
},
|
||||
set url(val) {
|
||||
this._properties.url = val;
|
||||
if (this.list) {
|
||||
this.commit();
|
||||
}
|
||||
this._properties.url = normalizeURI(val).spec;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -476,10 +525,7 @@ ReadingListItem.prototype = {
|
||||
undefined;
|
||||
},
|
||||
set uri(val) {
|
||||
this.url = val.spec;
|
||||
if (this.list) {
|
||||
this.commit();
|
||||
}
|
||||
this.url = normalizeURI(val).spec;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -502,10 +548,7 @@ ReadingListItem.prototype = {
|
||||
return this._properties.resolvedURL;
|
||||
},
|
||||
set resolvedURL(val) {
|
||||
this._properties.resolvedURL = val;
|
||||
if (this.list) {
|
||||
this.commit();
|
||||
}
|
||||
this._properties.resolvedURL = normalizeURI(val).spec;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -519,9 +562,6 @@ ReadingListItem.prototype = {
|
||||
},
|
||||
set resolvedURI(val) {
|
||||
this.resolvedURL = val.spec;
|
||||
if (this.list) {
|
||||
this.commit();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -533,9 +573,6 @@ ReadingListItem.prototype = {
|
||||
},
|
||||
set title(val) {
|
||||
this._properties.title = val;
|
||||
if (this.list) {
|
||||
this.commit();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -547,9 +584,6 @@ ReadingListItem.prototype = {
|
||||
},
|
||||
set resolvedTitle(val) {
|
||||
this._properties.resolvedTitle = val;
|
||||
if (this.list) {
|
||||
this.commit();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -561,9 +595,6 @@ ReadingListItem.prototype = {
|
||||
},
|
||||
set excerpt(val) {
|
||||
this._properties.excerpt = val;
|
||||
if (this.list) {
|
||||
this.commit();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -575,9 +606,6 @@ ReadingListItem.prototype = {
|
||||
},
|
||||
set status(val) {
|
||||
this._properties.status = val;
|
||||
if (this.list) {
|
||||
this.commit();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -589,9 +617,6 @@ ReadingListItem.prototype = {
|
||||
},
|
||||
set favorite(val) {
|
||||
this._properties.favorite = !!val;
|
||||
if (this.list) {
|
||||
this.commit();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -603,9 +628,6 @@ ReadingListItem.prototype = {
|
||||
},
|
||||
set isArticle(val) {
|
||||
this._properties.isArticle = !!val;
|
||||
if (this.list) {
|
||||
this.commit();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -617,9 +639,6 @@ ReadingListItem.prototype = {
|
||||
},
|
||||
set wordCount(val) {
|
||||
this._properties.wordCount = val;
|
||||
if (this.list) {
|
||||
this.commit();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -631,9 +650,6 @@ ReadingListItem.prototype = {
|
||||
},
|
||||
set unread(val) {
|
||||
this._properties.unread = !!val;
|
||||
if (this.list) {
|
||||
this.commit();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -647,9 +663,6 @@ ReadingListItem.prototype = {
|
||||
},
|
||||
set addedOn(val) {
|
||||
this._properties.addedOn = val.valueOf();
|
||||
if (this.list) {
|
||||
this.commit();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -663,9 +676,6 @@ ReadingListItem.prototype = {
|
||||
},
|
||||
set storedOn(val) {
|
||||
this._properties.storedOn = val.valueOf();
|
||||
if (this.list) {
|
||||
this.commit();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -677,9 +687,6 @@ ReadingListItem.prototype = {
|
||||
},
|
||||
set markedReadBy(val) {
|
||||
this._properties.markedReadBy = val;
|
||||
if (this.list) {
|
||||
this.commit();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -693,9 +700,6 @@ ReadingListItem.prototype = {
|
||||
},
|
||||
set markedReadOn(val) {
|
||||
this._properties.markedReadOn = val.valueOf();
|
||||
if (this.list) {
|
||||
this.commit();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -707,25 +711,24 @@ ReadingListItem.prototype = {
|
||||
},
|
||||
set readPosition(val) {
|
||||
this._properties.readPosition = val;
|
||||
if (this.list) {
|
||||
this.commit();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the given properties of the item, optionally calling commit().
|
||||
* Sets the given properties of the item, optionally calling list.updateItem().
|
||||
*
|
||||
* @param props A simple object containing the properties to set.
|
||||
* @param commit If true, commit() is called.
|
||||
* @return Promise<null> If commit is true, resolved when the commit
|
||||
* @param update If true, updateItem() is called for this item.
|
||||
* @return Promise<null> If update is true, resolved when the update
|
||||
* completes; otherwise resolved immediately.
|
||||
*/
|
||||
setProperties: Task.async(function* (props, commit=true) {
|
||||
setProperties: Task.async(function* (props, update=true) {
|
||||
for (let name in props) {
|
||||
this._properties[name] = props[name];
|
||||
}
|
||||
if (commit) {
|
||||
yield this.commit();
|
||||
// make sure everything is normalized.
|
||||
normalizeReadingListProperties(this._properties);
|
||||
if (update) {
|
||||
yield this.list.updateItem(this);
|
||||
}
|
||||
}),
|
||||
|
||||
@ -740,17 +743,6 @@ ReadingListItem.prototype = {
|
||||
this.delete = () => Promise.reject("The item has already been deleted");
|
||||
}),
|
||||
|
||||
/**
|
||||
* Notifies the item's list that the item has changed so that the list can
|
||||
* update itself.
|
||||
*
|
||||
* @return Promise<null> Resolved when the list has been updated.
|
||||
*/
|
||||
commit: Task.async(function* () {
|
||||
this._ensureBelongsToList();
|
||||
yield this.list.updateItem(this);
|
||||
}),
|
||||
|
||||
toJSON() {
|
||||
return this._properties;
|
||||
},
|
||||
@ -855,6 +847,23 @@ ReadingListItemIterator.prototype = {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalize a URI, stripping away extraneous parts we don't want to store
|
||||
* or compare against.
|
||||
*
|
||||
* @param {nsIURI/String} uri - URI to normalize.
|
||||
* @returns {nsIURI} Cloned and normalized version of the input URI.
|
||||
*/
|
||||
function normalizeURI(uri) {
|
||||
if (typeof uri == "string") {
|
||||
uri = Services.io.newURI(uri, "", null);
|
||||
}
|
||||
uri = uri.cloneIgnoringRef();
|
||||
try {
|
||||
uri.userPass = "";
|
||||
} catch (ex) {} // Not all nsURI impls (eg, nsSimpleURI) support .userPass
|
||||
return uri;
|
||||
};
|
||||
|
||||
function stripNonItemProperties(item) {
|
||||
let obj = {};
|
||||
@ -885,6 +894,24 @@ function clone(obj) {
|
||||
return Cu.cloneInto(obj, {}, { cloneFunctions: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get page metadata from the content document in a given <xul:browser>.
|
||||
* @see PageMetadata.jsm
|
||||
*
|
||||
* @param {<xul:browser>} browser - Browser element for the document.
|
||||
* @returns {Promise} Promise that is fulfilled with an object describing the metadata.
|
||||
*/
|
||||
function getMetadataFromBrowser(browser) {
|
||||
let mm = browser.messageManager;
|
||||
return new Promise(resolve => {
|
||||
function handleResult(msg) {
|
||||
mm.removeMessageListener("PageMetadata:PageDataResult", handleResult);
|
||||
resolve(msg.json);
|
||||
}
|
||||
mm.addMessageListener("PageMetadata:PageDataResult", handleResult);
|
||||
mm.sendAsyncMessage("PageMetadata:GetPageData");
|
||||
});
|
||||
}
|
||||
|
||||
Object.defineProperty(this, "ReadingList", {
|
||||
get() {
|
||||
|
@ -122,10 +122,11 @@ add_task(function* constraints() {
|
||||
checkError(err);
|
||||
|
||||
// update an item with an existing url
|
||||
item.guid = gItems[1].guid;
|
||||
let rlitem = yield gList.getItemForURL(gItems[0].url);
|
||||
rlitem.guid = gItems[1].guid;
|
||||
err = null;
|
||||
try {
|
||||
yield gList.updateItem(item);
|
||||
yield gList.updateItem(rlitem);
|
||||
}
|
||||
catch (e) {
|
||||
err = e;
|
||||
@ -145,10 +146,11 @@ add_task(function* constraints() {
|
||||
checkError(err);
|
||||
|
||||
// update an item with an existing resolvedURL
|
||||
item.url = gItems[1].url;
|
||||
rlitem = yield gList.getItemForURL(gItems[0].url);
|
||||
rlitem.url = gItems[1].url;
|
||||
err = null;
|
||||
try {
|
||||
yield gList.updateItem(item);
|
||||
yield gList.updateItem(rlitem);
|
||||
}
|
||||
catch (e) {
|
||||
err = e;
|
||||
@ -159,35 +161,33 @@ add_task(function* constraints() {
|
||||
item = kindOfClone(gItems[0]);
|
||||
delete item.guid;
|
||||
err = null;
|
||||
let rlitem1;
|
||||
try {
|
||||
yield gList.addItem(item);
|
||||
rlitem1 = yield gList.addItem(item);
|
||||
}
|
||||
catch (e) {
|
||||
err = e;
|
||||
}
|
||||
Assert.ok(!err, err ? err.message : undefined);
|
||||
let item1 = item;
|
||||
|
||||
// add a second item with no guid, which is allowed
|
||||
item = kindOfClone(gItems[1]);
|
||||
delete item.guid;
|
||||
err = null;
|
||||
let rlitem2;
|
||||
try {
|
||||
yield gList.addItem(item);
|
||||
rlitem2 = yield gList.addItem(item);
|
||||
}
|
||||
catch (e) {
|
||||
err = e;
|
||||
}
|
||||
Assert.ok(!err, err ? err.message : undefined);
|
||||
let item2 = item;
|
||||
|
||||
// Delete both items since other tests assume the store contains only gItems.
|
||||
item1.list = gList;
|
||||
item2.list = gList;
|
||||
yield gList.deleteItem(item1);
|
||||
yield gList.deleteItem(item2);
|
||||
yield gList.deleteItem(rlitem1);
|
||||
yield gList.deleteItem(rlitem2);
|
||||
let items = [];
|
||||
yield gList.forEachItem(i => items.push(i), { url: [item1.url, item2.url] });
|
||||
yield gList.forEachItem(i => items.push(i), { url: [rlitem1.url, rlitem2.url] });
|
||||
Assert.equal(items.length, 0);
|
||||
|
||||
// add a new item with no url
|
||||
@ -513,15 +513,12 @@ add_task(function* updateItem() {
|
||||
guid: gItems[0].guid,
|
||||
});
|
||||
Assert.equal(items.length, 1);
|
||||
let item = {
|
||||
_properties: items[0]._properties,
|
||||
list: items[0].list,
|
||||
};
|
||||
let item = items[0];
|
||||
|
||||
// update its title
|
||||
let newTitle = "updateItem new title";
|
||||
Assert.notEqual(item.title, newTitle);
|
||||
item._properties.title = newTitle;
|
||||
item.title = newTitle;
|
||||
yield gList.updateItem(item);
|
||||
|
||||
// get the item again
|
||||
@ -542,7 +539,7 @@ add_task(function* item_setProperties() {
|
||||
let item = (yield iter.items(1))[0];
|
||||
Assert.ok(item);
|
||||
|
||||
// item.setProperties(commit=false). After fetching the item again, its title
|
||||
// item.setProperties(update=false). After fetching the item again, its title
|
||||
// should be the old title.
|
||||
let oldTitle = item.title;
|
||||
let newTitle = "item_setProperties title 1";
|
||||
@ -556,7 +553,7 @@ add_task(function* item_setProperties() {
|
||||
Assert.ok(item === sameItem);
|
||||
Assert.equal(sameItem.title, oldTitle);
|
||||
|
||||
// item.setProperties(commit=true). After fetching the item again, its title
|
||||
// item.setProperties(update=true). After fetching the item again, its title
|
||||
// should be the new title.
|
||||
newTitle = "item_setProperties title 2";
|
||||
item.setProperties({ title: newTitle }, true);
|
||||
@ -572,6 +569,7 @@ add_task(function* item_setProperties() {
|
||||
// be the new title.
|
||||
newTitle = "item_setProperties title 3";
|
||||
item.title = newTitle;
|
||||
gList.updateItem(item);
|
||||
Assert.equal(item.title, newTitle);
|
||||
iter = gList.iterator({
|
||||
sort: "guid",
|
||||
@ -582,6 +580,7 @@ add_task(function* item_setProperties() {
|
||||
});
|
||||
|
||||
add_task(function* listeners() {
|
||||
Assert.equal((yield gList.count()), gItems.length);
|
||||
// add an item
|
||||
let resolve;
|
||||
let listenerPromise = new Promise(r => resolve = r);
|
||||
@ -594,6 +593,7 @@ add_task(function* listeners() {
|
||||
Assert.ok(items[0]);
|
||||
Assert.ok(items[0] === items[1]);
|
||||
gList.removeListener(listener);
|
||||
Assert.equal((yield gList.count()), gItems.length + 1);
|
||||
|
||||
// update an item
|
||||
listenerPromise = new Promise(r => resolve = r);
|
||||
@ -602,10 +602,12 @@ add_task(function* listeners() {
|
||||
};
|
||||
gList.addListener(listener);
|
||||
items[0].title = "listeners new title";
|
||||
gList.updateItem(items[0]);
|
||||
let listenerItem = yield listenerPromise;
|
||||
Assert.ok(listenerItem);
|
||||
Assert.ok(listenerItem === items[0]);
|
||||
gList.removeListener(listener);
|
||||
Assert.equal((yield gList.count()), gItems.length + 1);
|
||||
|
||||
// delete an item
|
||||
listenerPromise = new Promise(r => resolve = r);
|
||||
@ -618,6 +620,7 @@ add_task(function* listeners() {
|
||||
Assert.ok(listenerItem);
|
||||
Assert.ok(listenerItem === items[0]);
|
||||
gList.removeListener(listener);
|
||||
Assert.equal((yield gList.count()), gItems.length);
|
||||
});
|
||||
|
||||
// This test deletes items so it should probably run last.
|
||||
@ -638,7 +641,7 @@ add_task(function* deleteItem() {
|
||||
checkItems(items, gItems.slice(1));
|
||||
|
||||
// delete second item with list.deleteItem()
|
||||
yield gList.deleteItem(gItems[1]);
|
||||
yield gList.deleteItem(items[0]);
|
||||
gItems[1].list = null;
|
||||
Assert.equal((yield gList.count()), gItems.length - 2);
|
||||
items = [];
|
||||
@ -648,7 +651,7 @@ add_task(function* deleteItem() {
|
||||
checkItems(items, gItems.slice(2));
|
||||
|
||||
// delete third item with list.deleteItem()
|
||||
yield gList.deleteItem(gItems[2]);
|
||||
yield gList.deleteItem(items[0]);
|
||||
gItems[2].list = null;
|
||||
Assert.equal((yield gList.count()), gItems.length - 3);
|
||||
items = [];
|
||||
@ -673,7 +676,7 @@ function checkItems(actualItems, expectedItems) {
|
||||
|
||||
function checkError(err) {
|
||||
Assert.ok(err);
|
||||
Assert.ok(err instanceof Cu.getGlobalForObject(Sqlite).Error);
|
||||
Assert.ok(err instanceof Cu.getGlobalForObject(Sqlite).Error, err);
|
||||
}
|
||||
|
||||
function kindOfClone(item) {
|
||||
|
@ -60,12 +60,12 @@ let ReaderParent = {
|
||||
break;
|
||||
}
|
||||
case "Reader:ListStatusRequest":
|
||||
ReadingList.count(message.data).then(count => {
|
||||
ReadingList.containsURL(message.data.url).then(inList => {
|
||||
let mm = message.target.messageManager
|
||||
// Make sure the target browser is still alive before trying to send data back.
|
||||
if (mm) {
|
||||
mm.sendAsyncMessage("Reader:ListStatusData",
|
||||
{ inReadingList: !!count, url: message.data.url });
|
||||
{ inReadingList: inList, url: message.data.url });
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
@ -1629,6 +1629,8 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
|
||||
list-style-image: url("chrome://browser/skin/Info.png");
|
||||
}
|
||||
|
||||
%include ../shared/readinglist.inc.css
|
||||
|
||||
/* Reader mode button */
|
||||
|
||||
#reader-mode-button {
|
||||
|
@ -91,6 +91,7 @@ browser.jar:
|
||||
skin/classic/browser/tab-crashed.svg (../shared/incontent-icons/tab-crashed.svg)
|
||||
skin/classic/browser/welcome-back.svg (../shared/incontent-icons/welcome-back.svg)
|
||||
skin/classic/browser/reader-mode-16.png (../shared/reader/reader-mode-16.png)
|
||||
skin/classic/browser/readinglist/icons.svg (../shared/readinglist/icons.svg)
|
||||
skin/classic/browser/readinglist/readinglist-icon.svg (../shared/readinglist/readinglist-icon.svg)
|
||||
skin/classic/browser/readinglist/sidebar.css (../shared/readinglist/sidebar.css)
|
||||
skin/classic/browser/webRTC-shareDevice-16.png
|
||||
|
@ -2523,6 +2523,8 @@ richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-
|
||||
}
|
||||
}
|
||||
|
||||
%include ../shared/readinglist.inc.css
|
||||
|
||||
/* Reader mode button */
|
||||
|
||||
#reader-mode-button {
|
||||
|
@ -142,6 +142,7 @@ browser.jar:
|
||||
skin/classic/browser/welcome-back.svg (../shared/incontent-icons/welcome-back.svg)
|
||||
skin/classic/browser/reader-mode-16.png (../shared/reader/reader-mode-16.png)
|
||||
skin/classic/browser/reader-mode-16@2x.png (../shared/reader/reader-mode-16@2x.png)
|
||||
skin/classic/browser/readinglist/icons.svg (../shared/readinglist/icons.svg)
|
||||
skin/classic/browser/readinglist/readinglist-icon.svg (../shared/readinglist/readinglist-icon.svg)
|
||||
skin/classic/browser/readinglist/sidebar.css (../shared/readinglist/sidebar.css)
|
||||
skin/classic/browser/webRTC-shareDevice-16.png
|
||||
|
38
browser/themes/shared/readinglist.inc.css
Normal file
38
browser/themes/shared/readinglist.inc.css
Normal file
@ -0,0 +1,38 @@
|
||||
/* Reading List button */
|
||||
|
||||
#readinglist-addremove-button {
|
||||
-moz-appearance: none;
|
||||
border: none;
|
||||
list-style-image: url("chrome://browser/skin/readinglist/icons.svg#addpage");
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
#readinglist-addremove-button:hover {
|
||||
border: none;
|
||||
}
|
||||
|
||||
#readinglist-addremove-button > .toolbarbutton-icon {
|
||||
width: 16px;
|
||||
height: 16px
|
||||
}
|
||||
|
||||
#readinglist-addremove-button:not([already-added="true"]):hover {
|
||||
list-style-image: url("chrome://browser/skin/readinglist/icons.svg#addpage-hover");
|
||||
}
|
||||
|
||||
#readinglist-addremove-button:not([already-added="true"]):active {
|
||||
list-style-image: url("chrome://browser/skin/readinglist/icons.svg#addpage-active");
|
||||
}
|
||||
|
||||
#readinglist-addremove-button[already-added="true"] {
|
||||
list-style-image: url("chrome://browser/skin/readinglist/icons.svg#alreadyadded");
|
||||
}
|
||||
|
||||
#readinglist-addremove-button[already-added="true"]:hover {
|
||||
list-style-image: url("chrome://browser/skin/readinglist/icons.svg#alreadyadded-hover");
|
||||
}
|
||||
|
||||
#readinglist-addremove-button[already-added="true"]:active {
|
||||
list-style-image: url("chrome://browser/skin/readinglist/icons.svg#alreadyadded-active");
|
||||
}
|
||||
|
57
browser/themes/shared/readinglist/icons.svg
Normal file
57
browser/themes/shared/readinglist/icons.svg
Normal file
@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 16 16"
|
||||
xml:space="preserve">
|
||||
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
use:not(:target) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#addpage {
|
||||
fill: #808080;
|
||||
}
|
||||
#addpage-hover {
|
||||
fill: #555555;
|
||||
}
|
||||
#addpage-active {
|
||||
fill: #0095DD;
|
||||
}
|
||||
|
||||
#alreadyadded {
|
||||
fill: #0095DD;
|
||||
}
|
||||
#alreadyadded-hover {
|
||||
fill: #555555;
|
||||
}
|
||||
#alreadyadded-active {
|
||||
fill: #808080;
|
||||
}
|
||||
</style>
|
||||
|
||||
<mask id="plus-mask">
|
||||
<rect width="100%" height="100%" fill="white"/>
|
||||
<rect x="4" y="7.5" width="8" height="1"/>
|
||||
<rect x="7.5" y="4" width="1" height="8"/>
|
||||
</mask>
|
||||
|
||||
<g id="addpage-shape">
|
||||
<circle cx="8" cy="8" r="7" mask="url(#plus-mask)"/>
|
||||
</g>
|
||||
|
||||
</defs>
|
||||
|
||||
<use id="addpage" xlink:href="#addpage-shape"/>
|
||||
<use id="addpage-hover" xlink:href="#addpage-shape"/>
|
||||
<use id="addpage-active" xlink:href="#addpage-shape"/>
|
||||
|
||||
<use id="alreadyadded" xlink:href="#addpage-shape"/>
|
||||
<use id="alreadyadded-hover" xlink:href="#addpage-shape"/>
|
||||
<use id="alreadyadded-active" xlink:href="#addpage-shape"/>
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -1576,6 +1576,8 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
|
||||
-moz-image-region: rect(0, 48px, 16px, 32px);
|
||||
}
|
||||
|
||||
%include ../shared/readinglist.inc.css
|
||||
|
||||
/* Reader mode button */
|
||||
|
||||
#reader-mode-button {
|
||||
|
@ -110,6 +110,7 @@ browser.jar:
|
||||
skin/classic/browser/tab-crashed.svg (../shared/incontent-icons/tab-crashed.svg)
|
||||
skin/classic/browser/welcome-back.svg (../shared/incontent-icons/welcome-back.svg)
|
||||
skin/classic/browser/reader-mode-16.png (../shared/reader/reader-mode-16.png)
|
||||
skin/classic/browser/readinglist/icons.svg (../shared/readinglist/icons.svg)
|
||||
skin/classic/browser/readinglist/readinglist-icon.svg (../shared/readinglist/readinglist-icon.svg)
|
||||
skin/classic/browser/readinglist/sidebar.css (../shared/readinglist/sidebar.css)
|
||||
skin/classic/browser/notification-pluginNormal.png (../shared/plugins/notification-pluginNormal.png)
|
||||
@ -577,6 +578,7 @@ browser.jar:
|
||||
skin/classic/aero/browser/tab-crashed.svg (../shared/incontent-icons/tab-crashed.svg)
|
||||
skin/classic/aero/browser/welcome-back.svg (../shared/incontent-icons/welcome-back.svg)
|
||||
skin/classic/aero/browser/reader-mode-16.png (../shared/reader/reader-mode-16.png)
|
||||
skin/classic/aero/browser/readinglist/icons.svg (../shared/readinglist/icons.svg)
|
||||
skin/classic/aero/browser/readinglist/readinglist-icon.svg (../shared/readinglist/readinglist-icon.svg)
|
||||
skin/classic/aero/browser/readinglist/sidebar.css (../shared/readinglist/sidebar.css)
|
||||
skin/classic/aero/browser/notification-pluginNormal.png (../shared/plugins/notification-pluginNormal.png)
|
||||
|
@ -10,7 +10,7 @@ this.EXPORTED_SYMBOLS = [
|
||||
"ContentTask"
|
||||
];
|
||||
|
||||
const Cu = Components.utils;
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
|
||||
/**
|
||||
@ -50,13 +50,12 @@ this.ContentTask = {
|
||||
* @rejects An error message if execution fails.
|
||||
*/
|
||||
spawn: function ContentTask_spawn(browser, arg, task) {
|
||||
if(!gScriptLoadedSet.has(browser)) {
|
||||
if(!gScriptLoadedSet.has(browser.permanentKey)) {
|
||||
let mm = browser.messageManager;
|
||||
mm.addMessageListener("content-task:complete", ContentMessageListener);
|
||||
mm.loadFrameScript(
|
||||
"chrome://mochikit/content/tests/BrowserTestUtils/content-task.js", true);
|
||||
|
||||
gScriptLoadedSet.add(browser);
|
||||
gScriptLoadedSet.add(browser.permanentKey);
|
||||
}
|
||||
|
||||
let deferred = {};
|
||||
@ -93,3 +92,5 @@ let ContentMessageListener = {
|
||||
}
|
||||
},
|
||||
};
|
||||
Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager)
|
||||
.addMessageListener("content-task:complete", ContentMessageListener);
|
||||
|
Loading…
Reference in New Issue
Block a user