mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
merge fx-team to mozilla-central a=merge
This commit is contained in:
commit
1b9bf0226b
@ -240,6 +240,11 @@
|
||||
label="&emailVideoCmd.label;"
|
||||
accesskey="&emailVideoCmd.accesskey;"
|
||||
oncommand="gContextMenu.sendMedia();"/>
|
||||
<menu id="context-castvideo"
|
||||
label="&castVideoCmd.label;"
|
||||
accesskey="&castVideoCmd.accesskey;">
|
||||
<menupopup id="context-castvideo-popup" onpopupshowing="gContextMenu.populateCastVideoMenu(this)"/>
|
||||
</menu>
|
||||
<menuitem id="context-sendaudio"
|
||||
label="&emailAudioCmd.label;"
|
||||
accesskey="&emailAudioCmd.accesskey;"
|
||||
|
@ -47,10 +47,6 @@ var tabPreviews = {
|
||||
},
|
||||
|
||||
capture: function tabPreviews_capture(aTab, aShouldCache) {
|
||||
// Bug 863512 - Make page thumbnails work in electrolysis
|
||||
if (gMultiProcessBrowser)
|
||||
return new Image();
|
||||
|
||||
let browser = aTab.linkedBrowser;
|
||||
let uri = browser.currentURI.spec;
|
||||
|
||||
@ -77,7 +73,7 @@ var tabPreviews = {
|
||||
aTab.__thumbnail_lastURI = uri;
|
||||
}
|
||||
|
||||
PageThumbs.captureToCanvas(aTab.linkedBrowser.contentWindow, canvas);
|
||||
PageThumbs.captureToCanvas(browser, canvas);
|
||||
return canvas;
|
||||
},
|
||||
|
||||
|
@ -33,10 +33,6 @@ let gBrowserThumbnails = {
|
||||
_tabEvents: ["TabClose", "TabSelect"],
|
||||
|
||||
init: function Thumbnails_init() {
|
||||
// Bug 863512 - Make page thumbnails work in electrolysis
|
||||
if (gMultiProcessBrowser)
|
||||
return;
|
||||
|
||||
PageThumbs.addExpirationFilter(this);
|
||||
gBrowser.addTabsProgressListener(this);
|
||||
Services.prefs.addObserver(this.PREF_DISK_CACHE_SSL, this, false);
|
||||
@ -52,10 +48,6 @@ let gBrowserThumbnails = {
|
||||
},
|
||||
|
||||
uninit: function Thumbnails_uninit() {
|
||||
// Bug 863512 - Make page thumbnails work in electrolysis
|
||||
if (gMultiProcessBrowser)
|
||||
return;
|
||||
|
||||
PageThumbs.removeExpirationFilter(this);
|
||||
gBrowser.removeTabsProgressListener(this);
|
||||
Services.prefs.removeObserver(this.PREF_DISK_CACHE_SSL, this);
|
||||
@ -125,10 +117,6 @@ let gBrowserThumbnails = {
|
||||
|
||||
// FIXME: This should be part of the PageThumbs API. (bug 1062414)
|
||||
_shouldCapture: function Thumbnails_shouldCapture(aBrowser) {
|
||||
// Don't try to capture in e10s yet (because of bug 698371)
|
||||
if (gMultiProcessBrowser)
|
||||
return false;
|
||||
|
||||
// Capture only if it's the currently selected tab.
|
||||
if (aBrowser != gBrowser.selectedBrowser)
|
||||
return false;
|
||||
@ -144,14 +132,18 @@ let gBrowserThumbnails = {
|
||||
if (doc instanceof SVGDocument || doc instanceof XMLDocument)
|
||||
return false;
|
||||
|
||||
// There's no point in taking screenshot of loading pages.
|
||||
if (aBrowser.docShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE)
|
||||
return false;
|
||||
|
||||
// Don't take screenshots of about: pages.
|
||||
if (aBrowser.currentURI.schemeIs("about"))
|
||||
return false;
|
||||
|
||||
// FIXME e10s work around, we need channel information. bug 1073957
|
||||
if (!aBrowser.docShell)
|
||||
return true;
|
||||
|
||||
// There's no point in taking screenshot of loading pages.
|
||||
if (aBrowser.docShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE)
|
||||
return false;
|
||||
|
||||
let channel = aBrowser.docShell.currentDocumentChannel;
|
||||
|
||||
// No valid document channel. We shouldn't take a screenshot.
|
||||
|
@ -188,6 +188,12 @@ XPCOMUtils.defineLazyModuleGetter(this, "FormValidationHandler",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UITour",
|
||||
"resource:///modules/UITour.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CastingApps",
|
||||
"resource:///modules/CastingApps.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SimpleServiceDiscovery",
|
||||
"resource://gre/modules/SimpleServiceDiscovery.jsm");
|
||||
|
||||
let gInitialPages = [
|
||||
"about:blank",
|
||||
"about:newtab",
|
||||
|
@ -208,9 +208,19 @@ nsContextMenu.prototype = {
|
||||
// Send media URL (but not for canvas, since it's a big data: URL)
|
||||
this.showItem("context-sendimage", this.onImage);
|
||||
this.showItem("context-sendvideo", this.onVideo);
|
||||
this.showItem("context-castvideo", this.onVideo);
|
||||
this.showItem("context-sendaudio", this.onAudio);
|
||||
this.setItemAttr("context-sendvideo", "disabled", !this.mediaURL);
|
||||
this.setItemAttr("context-sendaudio", "disabled", !this.mediaURL);
|
||||
// getServicesForVideo alone would be sufficient here (it depends on
|
||||
// SimpleServiceDiscovery.services), but SimpleServiceDiscovery is garanteed
|
||||
// to be already loaded, since we load it on startup, and CastingApps isn't,
|
||||
// so check SimpleServiceDiscovery.services first to avoid needing to load
|
||||
// CastingApps.jsm if we don't need to.
|
||||
let shouldShowCast = this.mediaURL &&
|
||||
SimpleServiceDiscovery.services.length > 0 &&
|
||||
CastingApps.getServicesForVideo(this.target).length > 0;
|
||||
this.setItemAttr("context-castvideo", "disabled", !shouldShowCast);
|
||||
},
|
||||
|
||||
initViewItems: function CM_initViewItems() {
|
||||
@ -1316,6 +1326,25 @@ nsContextMenu.prototype = {
|
||||
MailIntegration.sendMessage(this.mediaURL, "");
|
||||
},
|
||||
|
||||
castVideo: function() {
|
||||
CastingApps.openExternal(this.target, window);
|
||||
},
|
||||
|
||||
populateCastVideoMenu: function(popup) {
|
||||
let videoEl = this.target;
|
||||
popup.innerHTML = null;
|
||||
let doc = popup.ownerDocument;
|
||||
let services = CastingApps.getServicesForVideo(videoEl);
|
||||
services.forEach(service => {
|
||||
let item = doc.createElement("menuitem");
|
||||
item.setAttribute("label", service.friendlyName);
|
||||
item.addEventListener("command", event => {
|
||||
CastingApps.sendVideoToService(videoEl, service);
|
||||
});
|
||||
popup.appendChild(item);
|
||||
});
|
||||
},
|
||||
|
||||
playPlugin: function() {
|
||||
gPluginHandler.contextMenuCommand(this.browser, this.target, "play");
|
||||
},
|
||||
|
@ -707,7 +707,7 @@
|
||||
canvas.mozOpaque = true;
|
||||
canvas.width = 160 * scale;
|
||||
canvas.height = 90 * scale;
|
||||
PageThumbs.captureToCanvas(chatbox.contentWindow, canvas);
|
||||
PageThumbs.captureToCanvas(chatbox, canvas);
|
||||
dt.setDragImage(canvas, -16 * scale, -16 * scale);
|
||||
|
||||
event.stopPropagation();
|
||||
|
@ -4530,7 +4530,7 @@
|
||||
canvas.height = 90 * scale;
|
||||
if (!gMultiProcessBrowser) {
|
||||
// Bug 863512 - Make page thumbnails work in e10s
|
||||
PageThumbs.captureToCanvas(browser.contentWindow, canvas);
|
||||
PageThumbs.captureToCanvas(browser, canvas);
|
||||
}
|
||||
dt.setDragImage(canvas, -16 * scale, -16 * scale);
|
||||
|
||||
|
@ -174,7 +174,9 @@ function runTest(testNum) {
|
||||
"---", null,
|
||||
"context-savevideo", true,
|
||||
"context-video-saveimage", true,
|
||||
"context-sendvideo", true
|
||||
"context-sendvideo", true,
|
||||
"context-castvideo", null,
|
||||
[], null
|
||||
].concat(inspectItems));
|
||||
closeContextMenu();
|
||||
openContextMenuFor(audio_in_video); // Invoke context menu for next test.
|
||||
@ -218,7 +220,9 @@ function runTest(testNum) {
|
||||
"---", null,
|
||||
"context-savevideo", true,
|
||||
"context-video-saveimage", false,
|
||||
"context-sendvideo", true
|
||||
"context-sendvideo", true,
|
||||
"context-castvideo", null,
|
||||
[], null
|
||||
].concat(inspectItems));
|
||||
closeContextMenu();
|
||||
openContextMenuFor(video_bad2); // Invoke context menu for next test.
|
||||
@ -242,7 +246,9 @@ function runTest(testNum) {
|
||||
"---", null,
|
||||
"context-savevideo", false,
|
||||
"context-video-saveimage", false,
|
||||
"context-sendvideo", false
|
||||
"context-sendvideo", false,
|
||||
"context-castvideo", null,
|
||||
[], null
|
||||
].concat(inspectItems));
|
||||
closeContextMenu();
|
||||
openContextMenuFor(iframe); // Invoke context menu for next test.
|
||||
@ -301,6 +307,8 @@ function runTest(testNum) {
|
||||
"context-savevideo", true,
|
||||
"context-video-saveimage", true,
|
||||
"context-sendvideo", true,
|
||||
"context-castvideo", null,
|
||||
[], null,
|
||||
"frame", null,
|
||||
["context-showonlythisframe", true,
|
||||
"context-openframeintab", true,
|
||||
|
@ -924,7 +924,7 @@ const CustomizableWidgets = [
|
||||
id: "loop-call-button",
|
||||
type: "custom",
|
||||
label: "loop-call-button3.label",
|
||||
tooltiptext: "loop-call-button2.tooltiptext",
|
||||
tooltiptext: "loop-call-button3.tooltiptext",
|
||||
defaultArea: CustomizableUI.AREA_NAVBAR,
|
||||
introducedInVersion: 1,
|
||||
onBuild: function(aDocument) {
|
||||
|
@ -99,6 +99,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerParent",
|
||||
"resource://gre/modules/LoginManagerParent.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SimpleServiceDiscovery",
|
||||
"resource://gre/modules/SimpleServiceDiscovery.jsm");
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SignInToWebsiteUX",
|
||||
"resource:///modules/SignInToWebsite.jsm");
|
||||
@ -747,8 +750,30 @@ BrowserGlue.prototype = {
|
||||
FormValidationHandler.uninit();
|
||||
},
|
||||
|
||||
_initServiceDiscovery: function () {
|
||||
var rokuDevice = {
|
||||
id: "roku:ecp",
|
||||
target: "roku:ecp",
|
||||
factory: function(aService) {
|
||||
Cu.import("resource://gre/modules/RokuApp.jsm");
|
||||
return new RokuApp(aService);
|
||||
},
|
||||
mirror: false,
|
||||
types: ["video/mp4"],
|
||||
extensions: ["mp4"]
|
||||
};
|
||||
|
||||
// Register targets
|
||||
SimpleServiceDiscovery.registerDevice(rokuDevice);
|
||||
|
||||
// Search for devices continuously every 120 seconds
|
||||
SimpleServiceDiscovery.search(120 * 1000);
|
||||
},
|
||||
|
||||
// All initial windows have opened.
|
||||
_onWindowsRestored: function BG__onWindowsRestored() {
|
||||
this._initServiceDiscovery();
|
||||
|
||||
// Show update notification, if needed.
|
||||
if (Services.prefs.prefHasUserValue("app.update.postupdate"))
|
||||
this._showUpdateNotification();
|
||||
@ -1344,7 +1369,7 @@ BrowserGlue.prototype = {
|
||||
},
|
||||
|
||||
_migrateUI: function BG__migrateUI() {
|
||||
const UI_VERSION = 23;
|
||||
const UI_VERSION = 24;
|
||||
const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
|
||||
let currentUIVersion = 0;
|
||||
try {
|
||||
@ -1422,15 +1447,6 @@ BrowserGlue.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
if (currentUIVersion < 8) {
|
||||
// Reset homepage pref for users who have it set to google.com/firefox
|
||||
let uri = Services.prefs.getComplexValue("browser.startup.homepage",
|
||||
Ci.nsIPrefLocalizedString).data;
|
||||
if (uri && /^https?:\/\/(www\.)?google(\.\w{2,3}){1,2}\/firefox\/?$/.test(uri)) {
|
||||
Services.prefs.clearUserPref("browser.startup.homepage");
|
||||
}
|
||||
}
|
||||
|
||||
if (currentUIVersion < 9) {
|
||||
// This code adds the customizable downloads buttons.
|
||||
let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
|
||||
@ -1495,10 +1511,6 @@ BrowserGlue.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
if (currentUIVersion < 13) {
|
||||
/* Obsolete */
|
||||
}
|
||||
|
||||
if (currentUIVersion < 14) {
|
||||
// DOM Storage doesn't specially handle about: pages anymore.
|
||||
let path = OS.Path.join(OS.Constants.Path.profileDir,
|
||||
@ -1593,6 +1605,18 @@ BrowserGlue.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
if (currentUIVersion < 24) {
|
||||
// Reset homepage pref for users who have it set to start.mozilla.org
|
||||
// or google.com/firefox.
|
||||
const HOMEPAGE_PREF = "browser.startup.homepage";
|
||||
let uri = Services.prefs.getComplexValue(HOMEPAGE_PREF,
|
||||
Ci.nsIPrefLocalizedString).data;
|
||||
if (uri && (uri.startsWith("http://start.mozilla.org") ||
|
||||
/^https?:\/\/(www\.)?google\.[a-z.]+\/firefox/i.test(uri))) {
|
||||
Services.prefs.clearUserPref(HOMEPAGE_PREF);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the migration version.
|
||||
Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
|
||||
},
|
||||
|
@ -313,6 +313,11 @@ nsGNOMEShellService::SetDefaultBrowser(bool aClaimAllTypes,
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
|
||||
if (prefs) {
|
||||
(void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -326,29 +331,25 @@ nsGNOMEShellService::GetShouldCheckDefaultBrowser(bool* aResult)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrefBranch> prefs;
|
||||
nsCOMPtr<nsIPrefService> pserve(do_GetService(NS_PREFSERVICE_CONTRACTID));
|
||||
if (pserve)
|
||||
pserve->GetBranch("", getter_AddRefs(prefs));
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (prefs)
|
||||
prefs->GetBoolPref(PREF_CHECKDEFAULTBROWSER, aResult);
|
||||
|
||||
return NS_OK;
|
||||
return prefs->GetBoolPref(PREF_CHECKDEFAULTBROWSER, aResult);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsGNOMEShellService::SetShouldCheckDefaultBrowser(bool aShouldCheck)
|
||||
{
|
||||
nsCOMPtr<nsIPrefBranch> prefs;
|
||||
nsCOMPtr<nsIPrefService> pserve(do_GetService(NS_PREFSERVICE_CONTRACTID));
|
||||
if (pserve)
|
||||
pserve->GetBranch("", getter_AddRefs(prefs));
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (prefs)
|
||||
prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, aShouldCheck);
|
||||
|
||||
return NS_OK;
|
||||
return prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, aShouldCheck);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
@ -90,7 +90,12 @@ nsMacShellService::SetDefaultBrowser(bool aClaimAllTypes, bool aForAllUsers)
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
|
||||
if (prefs) {
|
||||
(void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -104,27 +109,25 @@ nsMacShellService::GetShouldCheckDefaultBrowser(bool* aResult)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrefBranch> prefs;
|
||||
nsCOMPtr<nsIPrefService> pserve(do_GetService(NS_PREFSERVICE_CONTRACTID));
|
||||
if (pserve)
|
||||
pserve->GetBranch("", getter_AddRefs(prefs));
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
prefs->GetBoolPref(PREF_CHECKDEFAULTBROWSER, aResult);
|
||||
|
||||
return NS_OK;
|
||||
return prefs->GetBoolPref(PREF_CHECKDEFAULTBROWSER, aResult);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMacShellService::SetShouldCheckDefaultBrowser(bool aShouldCheck)
|
||||
{
|
||||
nsCOMPtr<nsIPrefBranch> prefs;
|
||||
nsCOMPtr<nsIPrefService> pserve(do_GetService(NS_PREFSERVICE_CONTRACTID));
|
||||
if (pserve)
|
||||
pserve->GetBranch("", getter_AddRefs(prefs));
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, aShouldCheck);
|
||||
|
||||
return NS_OK;
|
||||
return prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, aShouldCheck);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
@ -275,20 +275,15 @@ nsWindowsShellService::ShortcutMaintenance()
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
|
||||
NS_NAMED_LITERAL_CSTRING(prefName, "browser.taskbar.lastgroupid");
|
||||
nsCOMPtr<nsIPrefService> prefs =
|
||||
nsCOMPtr<nsIPrefBranch> prefs =
|
||||
do_GetService(NS_PREFSERVICE_CONTRACTID);
|
||||
if (!prefs)
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
|
||||
nsCOMPtr<nsIPrefBranch> prefBranch;
|
||||
prefs->GetBranch(nullptr, getter_AddRefs(prefBranch));
|
||||
if (!prefBranch)
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
|
||||
nsCOMPtr<nsISupportsString> prefString;
|
||||
rv = prefBranch->GetComplexValue(prefName.get(),
|
||||
NS_GET_IID(nsISupportsString),
|
||||
getter_AddRefs(prefString));
|
||||
rv = prefs->GetComplexValue(prefName.get(),
|
||||
NS_GET_IID(nsISupportsString),
|
||||
getter_AddRefs(prefString));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
nsAutoString version;
|
||||
prefString->GetData(version);
|
||||
@ -304,9 +299,9 @@ nsWindowsShellService::ShortcutMaintenance()
|
||||
return rv;
|
||||
|
||||
prefString->SetData(appId);
|
||||
rv = prefBranch->SetComplexValue(prefName.get(),
|
||||
NS_GET_IID(nsISupportsString),
|
||||
prefString);
|
||||
rv = prefs->SetComplexValue(prefName.get(),
|
||||
NS_GET_IID(nsISupportsString),
|
||||
prefString);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("Couldn't set last user model id!");
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
@ -707,6 +702,11 @@ nsWindowsShellService::SetDefaultBrowser(bool aClaimAllTypes, bool aForAllUsers)
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
|
||||
if (prefs) {
|
||||
(void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -722,13 +722,11 @@ nsWindowsShellService::GetShouldCheckDefaultBrowser(bool* aResult)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrefBranch> prefs;
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIPrefService> pserve(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = pserve->GetBranch("", getter_AddRefs(prefs));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return prefs->GetBoolPref(PREF_CHECKDEFAULTBROWSER, aResult);
|
||||
}
|
||||
@ -736,14 +734,11 @@ nsWindowsShellService::GetShouldCheckDefaultBrowser(bool* aResult)
|
||||
NS_IMETHODIMP
|
||||
nsWindowsShellService::SetShouldCheckDefaultBrowser(bool aShouldCheck)
|
||||
{
|
||||
nsCOMPtr<nsIPrefBranch> prefs;
|
||||
nsresult rv;
|
||||
|
||||
nsCOMPtr<nsIPrefService> pserve(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = pserve->GetBranch("", getter_AddRefs(prefs));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, aShouldCheck);
|
||||
}
|
||||
|
@ -1383,15 +1383,9 @@ TabCanvas.prototype = Utils.extend(new Subscribable(), {
|
||||
if (!w || !h)
|
||||
return;
|
||||
|
||||
if (!this.tab.linkedBrowser.contentWindow) {
|
||||
Utils.log('no tab.linkedBrowser.contentWindow in TabCanvas.paint()');
|
||||
return;
|
||||
}
|
||||
|
||||
let win = this.tab.linkedBrowser.contentWindow;
|
||||
gPageThumbnails.captureToCanvas(win, this.canvas);
|
||||
|
||||
this._sendToSubscribers("painted");
|
||||
gPageThumbnails.captureToCanvas(this.tab.linkedBrowser, this.canvas, () => {
|
||||
this._sendToSubscribers("painted");
|
||||
});
|
||||
},
|
||||
|
||||
// ----------
|
||||
|
@ -29,7 +29,7 @@ let test = Task.async(function*() {
|
||||
let selection = TimelineView.overview.getSelection();
|
||||
|
||||
is((selection.start) | 0,
|
||||
(markers[0].start * TimelineView.overview.dataScaleX) | 0,
|
||||
((markers[0].start - markers.startTime) * TimelineView.overview.dataScaleX) | 0,
|
||||
"The initial selection start is correct.");
|
||||
|
||||
is((selection.end - selection.start) | 0,
|
||||
|
@ -107,13 +107,19 @@ let TimelineController = {
|
||||
* Starts the recording, updating the UI as needed.
|
||||
*/
|
||||
_startRecording: function*() {
|
||||
this._markers = [];
|
||||
this._markers.startTime = performance.now();
|
||||
this._markers.endTime = performance.now();
|
||||
this._updateId = setInterval(this._onRecordingTick, OVERVIEW_UPDATE_INTERVAL);
|
||||
|
||||
TimelineView.handleRecordingStarted();
|
||||
yield gFront.start();
|
||||
let startTime = yield gFront.start();
|
||||
// Times must come from the actor in order to be self-consistent.
|
||||
// However, we also want to update the view with the elapsed time
|
||||
// even when the actor is not generating data. To do this we get
|
||||
// the local time and use it to compute a reasonable elapsed time.
|
||||
// See _onRecordingTick.
|
||||
this._localStartTime = performance.now();
|
||||
|
||||
this._markers = [];
|
||||
this._markers.startTime = startTime;
|
||||
this._markers.endTime = startTime;
|
||||
this._updateId = setInterval(this._onRecordingTick, OVERVIEW_UPDATE_INTERVAL);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -142,9 +148,12 @@ let TimelineController = {
|
||||
* @param array markers
|
||||
* A list of new markers collected since the last time this
|
||||
* function was invoked.
|
||||
* @param number endTime
|
||||
* A time after the last marker in markers was collected.
|
||||
*/
|
||||
_onMarkers: function(markers) {
|
||||
_onMarkers: function(markers, endTime) {
|
||||
Array.prototype.push.apply(this._markers, markers);
|
||||
this._markers.endTime = endTime;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -152,7 +161,13 @@ let TimelineController = {
|
||||
* Updates the markers store with the current time and the timeline overview.
|
||||
*/
|
||||
_onRecordingTick: function() {
|
||||
this._markers.endTime = performance.now();
|
||||
// Compute an approximate ending time for the view. This is
|
||||
// needed to ensure that the view updates even when new data is
|
||||
// not being generated.
|
||||
let fakeTime = this._markers.startTime + (performance.now() - this._localStartTime);
|
||||
if (fakeTime > this._markers.endTime) {
|
||||
this._markers.endTime = fakeTime;
|
||||
}
|
||||
TimelineView.handleMarkersUpdate(this._markers);
|
||||
}
|
||||
};
|
||||
@ -214,12 +229,12 @@ let TimelineView = {
|
||||
|
||||
let markers = TimelineController.getMarkers();
|
||||
if (markers.length) {
|
||||
let start = markers[0].start * this.overview.dataScaleX;
|
||||
let start = (markers[0].start - markers.startTime) * this.overview.dataScaleX;
|
||||
let end = start + this.overview.width * OVERVIEW_INITIAL_SELECTION_RATIO;
|
||||
this.overview.setSelection({ start, end });
|
||||
} else {
|
||||
let duration = markers.endTime - markers.startTime;
|
||||
this.waterfall.setData(markers, 0, duration);
|
||||
this.waterfall.setData(markers, markers.startTime, markers.endTime);
|
||||
}
|
||||
|
||||
window.emit(EVENTS.RECORDING_ENDED);
|
||||
@ -251,8 +266,8 @@ let TimelineView = {
|
||||
let end = selection.end / this.overview.dataScaleX;
|
||||
|
||||
let markers = TimelineController.getMarkers();
|
||||
let timeStart = Math.min(start, end);
|
||||
let timeEnd = Math.max(start, end);
|
||||
let timeStart = markers.startTime + Math.min(start, end);
|
||||
let timeEnd = markers.startTime + Math.max(start, end);
|
||||
this.waterfall.setData(markers, timeStart, timeEnd);
|
||||
},
|
||||
|
||||
|
@ -170,6 +170,9 @@ Overview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
ctx.beginPath();
|
||||
|
||||
for (let { start, end } of batch) {
|
||||
start -= this._data.startTime;
|
||||
end -= this._data.startTime;
|
||||
|
||||
let left = start * dataScale;
|
||||
let duration = Math.max(end - start, OVERVIEW_MARKER_DURATION_MIN);
|
||||
let width = Math.max(duration * dataScale, this._pixelRatio);
|
||||
|
@ -76,16 +76,17 @@ Waterfall.prototype = {
|
||||
* @param array markers
|
||||
* A list of markers received from the controller.
|
||||
* @param number timeStart
|
||||
* The delta time (in milliseconds) to start drawing from.
|
||||
* The time (in milliseconds) to start drawing from.
|
||||
* @param number timeEnd
|
||||
* The delta time (in milliseconds) to end drawing at.
|
||||
* The time (in milliseconds) to end drawing at.
|
||||
*/
|
||||
setData: function(markers, timeStart, timeEnd) {
|
||||
this.clearView();
|
||||
|
||||
let dataScale = this._waterfallWidth / (timeEnd - timeStart);
|
||||
this._drawWaterfallBackground(dataScale);
|
||||
this._buildHeader(this._headerContents, timeStart, dataScale);
|
||||
// Label the header as if the first possible marker was at T=0.
|
||||
this._buildHeader(this._headerContents, timeStart - markers.startTime, dataScale);
|
||||
this._buildMarkers(this._listContents, markers, timeStart, timeEnd, dataScale);
|
||||
},
|
||||
|
||||
|
@ -23,6 +23,7 @@ support-files =
|
||||
[browser_audionode-actor-is-source.js]
|
||||
[browser_audionode-actor-bypass.js]
|
||||
[browser_audionode-actor-connectnode-disconnect.js]
|
||||
[browser_audionode-actor-connectparam.js]
|
||||
[browser_webaudio-actor-simple.js]
|
||||
[browser_webaudio-actor-destroy-node.js]
|
||||
[browser_webaudio-actor-connect-param.js]
|
||||
|
@ -0,0 +1,35 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that AudioNodeActor#connectParam() work.
|
||||
* Uses the editor front as the actors do not retain connect state.
|
||||
*/
|
||||
|
||||
function spawnTest() {
|
||||
let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
|
||||
let { panelWin } = panel;
|
||||
let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin;
|
||||
|
||||
reload(target);
|
||||
|
||||
let [actors] = yield Promise.all([
|
||||
get3(gFront, "create-node"),
|
||||
waitForGraphRendered(panelWin, 3, 2)
|
||||
]);
|
||||
|
||||
let [dest, osc, gain] = actors;
|
||||
|
||||
yield osc.disconnect();
|
||||
|
||||
osc.connectParam(gain, "gain");
|
||||
yield Promise.all([
|
||||
waitForGraphRendered(panelWin, 3, 1, 1),
|
||||
once(gAudioNodes, "connect")
|
||||
]);
|
||||
ok(true, "Oscillator connect to Gain's Gain AudioParam, event emitted.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
||||
|
@ -30,13 +30,14 @@ function CloseUI() {
|
||||
}
|
||||
|
||||
function BuildUI(addons) {
|
||||
BuildItem(addons.adb, true /* is adb */);
|
||||
BuildItem(addons.adb, "adb");
|
||||
BuildItem(addons.adapters, "adapters");
|
||||
for (let addon of addons.simulators) {
|
||||
BuildItem(addon, false /* is adb */);
|
||||
BuildItem(addon, "simulator");
|
||||
}
|
||||
}
|
||||
|
||||
function BuildItem(addon, isADB) {
|
||||
function BuildItem(addon, type) {
|
||||
|
||||
function onAddonUpdate(event, arg) {
|
||||
switch (event) {
|
||||
@ -73,20 +74,29 @@ function BuildItem(addon, isADB) {
|
||||
let li = document.createElement("li");
|
||||
li.setAttribute("status", addon.status);
|
||||
|
||||
// Used in tests
|
||||
if (isADB) {
|
||||
li.setAttribute("addon", "adb");
|
||||
} else {
|
||||
li.setAttribute("addon", "simulator-" + addon.version);
|
||||
}
|
||||
|
||||
let name = document.createElement("span");
|
||||
name.className = "name";
|
||||
if (isADB) {
|
||||
name.textContent = Strings.GetStringFromName("addons_adb_label");
|
||||
} else {
|
||||
let stability = Strings.GetStringFromName("addons_" + addon.stability);
|
||||
name.textContent = Strings.formatStringFromName("addons_simulator_label", [addon.version, stability], 2);
|
||||
|
||||
switch (type) {
|
||||
case "adb":
|
||||
li.setAttribute("addon", type);
|
||||
name.textContent = Strings.GetStringFromName("addons_adb_label");
|
||||
break;
|
||||
case "adapters":
|
||||
li.setAttribute("addon", type);
|
||||
try {
|
||||
name.textContent = Strings.GetStringFromName("addons_adapters_label");
|
||||
} catch(e) {
|
||||
// This code (bug 1081093) will be backported to Aurora, which doesn't
|
||||
// contain this string.
|
||||
name.textContent = "Tools Adapters Add-on";
|
||||
}
|
||||
break;
|
||||
case "simulator":
|
||||
li.setAttribute("addon", "simulator-" + addon.version);
|
||||
let stability = Strings.GetStringFromName("addons_" + addon.stability);
|
||||
name.textContent = Strings.formatStringFromName("addons_simulator_label", [addon.version, stability], 2);
|
||||
break;
|
||||
}
|
||||
|
||||
li.appendChild(name);
|
||||
@ -111,7 +121,7 @@ function BuildItem(addon, isADB) {
|
||||
let progress = document.createElement("progress");
|
||||
li.appendChild(progress);
|
||||
|
||||
if (isADB) {
|
||||
if (type == "adb") {
|
||||
let warning = document.createElement("p");
|
||||
warning.textContent = Strings.GetStringFromName("addons_adb_warning");
|
||||
warning.className = "warning";
|
||||
|
@ -69,15 +69,22 @@ let UI = {
|
||||
this.autoSelectProject();
|
||||
});
|
||||
|
||||
// Auto install the ADB Addon Helper. Only once.
|
||||
// If the user decides to uninstall the addon, we won't install it again.
|
||||
let autoInstallADBHelper = Services.prefs.getBoolPref("devtools.webide.autoinstallADBHelper");
|
||||
if (autoInstallADBHelper && !Devices.helperAddonInstalled) {
|
||||
// Auto install the ADB Addon Helper and Tools Adapters. Only once.
|
||||
// If the user decides to uninstall any of this addon, we won't install it again.
|
||||
let autoinstallADBHelper = Services.prefs.getBoolPref("devtools.webide.autoinstallADBHelper");
|
||||
let autoinstallFxdtAdapters = Services.prefs.getBoolPref("devtools.webide.autoinstallFxdtAdapters");
|
||||
if (autoinstallADBHelper) {
|
||||
GetAvailableAddons().then(addons => {
|
||||
addons.adb.install();
|
||||
}, console.error);
|
||||
}
|
||||
if (autoinstallFxdtAdapters) {
|
||||
GetAvailableAddons().then(addons => {
|
||||
addons.adapters.install();
|
||||
}, console.error);
|
||||
}
|
||||
Services.prefs.setBoolPref("devtools.webide.autoinstallADBHelper", false);
|
||||
Services.prefs.setBoolPref("devtools.webide.autoinstallFxdtAdapters", false);
|
||||
|
||||
this.lastConnectedRuntime = Services.prefs.getCharPref("devtools.webide.lastConnectedRuntime");
|
||||
|
||||
|
@ -13,8 +13,10 @@ const {GetAddonsJSON} = require("devtools/webide/remote-resources");
|
||||
|
||||
let SIMULATOR_LINK = Services.prefs.getCharPref("devtools.webide.simulatorAddonsURL");
|
||||
let ADB_LINK = Services.prefs.getCharPref("devtools.webide.adbAddonURL");
|
||||
let ADAPTERS_LINK = Services.prefs.getCharPref("devtools.webide.adaptersAddonURL");
|
||||
let SIMULATOR_ADDON_ID = Services.prefs.getCharPref("devtools.webide.simulatorAddonID");
|
||||
let ADB_ADDON_ID = Services.prefs.getCharPref("devtools.webide.adbAddonID");
|
||||
let ADAPTERS_ADDON_ID = Services.prefs.getCharPref("devtools.webide.adaptersAddonID");
|
||||
|
||||
let platform = Services.appShell.hiddenDOMWindow.navigator.platform;
|
||||
let OS = "";
|
||||
@ -26,36 +28,24 @@ if (platform.indexOf("Win") != -1) {
|
||||
if (platform.indexOf("x86_64") != -1) {
|
||||
OS = "linux64";
|
||||
} else {
|
||||
OS = "linux";
|
||||
OS = "linux32";
|
||||
}
|
||||
}
|
||||
|
||||
Simulator.on("unregister", updateSimulatorAddons);
|
||||
Simulator.on("register", updateSimulatorAddons);
|
||||
Devices.on("addon-status-updated", updateAdbAddon);
|
||||
|
||||
function updateSimulatorAddons(event, version) {
|
||||
let addonsListener = {};
|
||||
addonsListener.onEnabled =
|
||||
addonsListener.onDisabled =
|
||||
addonsListener.onInstalled =
|
||||
addonsListener.onUninstalled = (updatedAddon) => {
|
||||
GetAvailableAddons().then(addons => {
|
||||
let foundAddon = null;
|
||||
for (let addon of addons.simulators) {
|
||||
if (addon.version == version) {
|
||||
foundAddon = addon;
|
||||
break;
|
||||
for (let a of [...addons.simulators, addons.adb, addons.adapters]) {
|
||||
if (a.addonID == updatedAddon.id) {
|
||||
a.updateInstallStatus();
|
||||
}
|
||||
}
|
||||
if (!foundAddon) {
|
||||
console.warn("An unknown simulator (un)registered", version);
|
||||
return;
|
||||
}
|
||||
foundAddon.updateInstallStatus();
|
||||
});
|
||||
}
|
||||
|
||||
function updateAdbAddon() {
|
||||
GetAvailableAddons().then(addons => {
|
||||
addons.adb.updateInstallStatus();
|
||||
});
|
||||
}
|
||||
AddonManager.addAddonListener(addonsListener);
|
||||
|
||||
let GetAvailableAddons_promise = null;
|
||||
let GetAvailableAddons = exports.GetAvailableAddons = function() {
|
||||
@ -73,6 +63,7 @@ let GetAvailableAddons = exports.GetAvailableAddons = function() {
|
||||
}
|
||||
}
|
||||
addons.adb = new ADBAddon();
|
||||
addons.adapters = new AdaptersAddon();
|
||||
deferred.resolve(addons);
|
||||
}, e => {
|
||||
GetAvailableAddons_promise = null;
|
||||
@ -99,13 +90,23 @@ Addon.prototype = {
|
||||
return this._status;
|
||||
},
|
||||
|
||||
install: function() {
|
||||
if (this.status != "uninstalled") {
|
||||
throw new Error("Not uninstalled");
|
||||
}
|
||||
this.status = "preparing";
|
||||
|
||||
updateInstallStatus: function() {
|
||||
AddonManager.getAddonByID(this.addonID, (addon) => {
|
||||
if (addon && !addon.userDisabled) {
|
||||
this.status = "installed";
|
||||
} else {
|
||||
this.status = "uninstalled";
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
install: function() {
|
||||
AddonManager.getAddonByID(this.addonID, (addon) => {
|
||||
if (addon && !addon.userDisabled) {
|
||||
this.status = "installed";
|
||||
return;
|
||||
}
|
||||
this.status = "preparing";
|
||||
if (addon && addon.userDisabled) {
|
||||
addon.userDisabled = false;
|
||||
} else {
|
||||
@ -115,7 +116,6 @@ Addon.prototype = {
|
||||
}, "application/x-xpinstall");
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
uninstall: function() {
|
||||
@ -167,44 +167,30 @@ function SimulatorAddon(stability, version) {
|
||||
EventEmitter.decorate(this);
|
||||
this.stability = stability;
|
||||
this.version = version;
|
||||
this.xpiLink = SIMULATOR_LINK.replace(/#OS#/g, OS)
|
||||
// This addon uses the string "linux" for "linux32"
|
||||
let fixedOS = OS == "linux32" ? "linux" : OS;
|
||||
this.xpiLink = SIMULATOR_LINK.replace(/#OS#/g, fixedOS)
|
||||
.replace(/#VERSION#/g, version)
|
||||
.replace(/#SLASHED_VERSION#/g, version.replace(/\./g, "_"));
|
||||
this.addonID = SIMULATOR_ADDON_ID.replace(/#SLASHED_VERSION#/g, version.replace(/\./g, "_"));
|
||||
this.updateInstallStatus();
|
||||
}
|
||||
|
||||
SimulatorAddon.prototype = Object.create(Addon.prototype, {
|
||||
updateInstallStatus: {
|
||||
enumerable: true,
|
||||
value: function() {
|
||||
let sim = Simulator.getByVersion(this.version);
|
||||
if (sim) {
|
||||
this.status = "installed";
|
||||
} else {
|
||||
this.status = "uninstalled";
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
SimulatorAddon.prototype = Object.create(Addon.prototype);
|
||||
|
||||
function ADBAddon() {
|
||||
EventEmitter.decorate(this);
|
||||
this.xpiLink = ADB_LINK.replace(/#OS#/g, OS);
|
||||
// This addon uses the string "linux" for "linux32"
|
||||
let fixedOS = OS == "linux32" ? "linux" : OS;
|
||||
this.xpiLink = ADB_LINK.replace(/#OS#/g, fixedOS);
|
||||
this.addonID = ADB_ADDON_ID;
|
||||
this.updateInstallStatus();
|
||||
}
|
||||
ADBAddon.prototype = Object.create(Addon.prototype);
|
||||
|
||||
ADBAddon.prototype = Object.create(Addon.prototype, {
|
||||
updateInstallStatus: {
|
||||
enumerable: true,
|
||||
value: function() {
|
||||
if (Devices.helperAddonInstalled) {
|
||||
this.status = "installed";
|
||||
} else {
|
||||
this.status = "uninstalled";
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function AdaptersAddon() {
|
||||
EventEmitter.decorate(this);
|
||||
this.xpiLink = ADAPTERS_LINK.replace(/#OS#/g, OS);
|
||||
this.addonID = ADAPTERS_ADDON_ID;
|
||||
this.updateInstallStatus();
|
||||
}
|
||||
AdaptersAddon.prototype = Object.create(Addon.prototype);
|
||||
|
BIN
browser/devtools/webide/test/addons/fxdt-adapters-linux32.xpi
Normal file
BIN
browser/devtools/webide/test/addons/fxdt-adapters-linux32.xpi
Normal file
Binary file not shown.
BIN
browser/devtools/webide/test/addons/fxdt-adapters-linux64.xpi
Normal file
BIN
browser/devtools/webide/test/addons/fxdt-adapters-linux64.xpi
Normal file
Binary file not shown.
BIN
browser/devtools/webide/test/addons/fxdt-adapters-mac64.xpi
Normal file
BIN
browser/devtools/webide/test/addons/fxdt-adapters-mac64.xpi
Normal file
Binary file not shown.
BIN
browser/devtools/webide/test/addons/fxdt-adapters-win32.xpi
Normal file
BIN
browser/devtools/webide/test/addons/fxdt-adapters-win32.xpi
Normal file
Binary file not shown.
@ -20,6 +20,10 @@ support-files =
|
||||
addons/adbhelper-linux64.xpi
|
||||
addons/adbhelper-win32.xpi
|
||||
addons/adbhelper-mac64.xpi
|
||||
addons/fxdt-adapters-linux32.xpi
|
||||
addons/fxdt-adapters-linux64.xpi
|
||||
addons/fxdt-adapters-win32.xpi
|
||||
addons/fxdt-adapters-mac64.xpi
|
||||
head.js
|
||||
hosted_app.manifest
|
||||
templates.json
|
||||
|
@ -27,25 +27,22 @@ Services.prefs.setBoolPref("devtools.webide.enableLocalRuntime", true);
|
||||
Services.prefs.setCharPref("devtools.webide.addonsURL", TEST_BASE + "addons/simulators.json");
|
||||
Services.prefs.setCharPref("devtools.webide.simulatorAddonsURL", TEST_BASE + "addons/fxos_#SLASHED_VERSION#_simulator-#OS#.xpi");
|
||||
Services.prefs.setCharPref("devtools.webide.adbAddonURL", TEST_BASE + "addons/adbhelper-#OS#.xpi");
|
||||
Services.prefs.setCharPref("devtools.webide.adaptersAddonURL", TEST_BASE + "addons/fxdt-adapters-#OS#.xpi");
|
||||
Services.prefs.setCharPref("devtools.webide.templatesURL", TEST_BASE + "templates.json");
|
||||
|
||||
|
||||
SimpleTest.registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("devtools.webide.templatesURL");
|
||||
Services.prefs.clearUserPref("devtools.webide.enabled");
|
||||
Services.prefs.clearUserPref("devtools.webide.enableLocalRuntime");
|
||||
Services.prefs.clearUserPref("devtools.webide.addonsURL");
|
||||
Services.prefs.clearUserPref("devtools.webide.simulatorAddonsURL");
|
||||
Services.prefs.clearUserPref("devtools.webide.adbAddonURL");
|
||||
Services.prefs.clearUserPref("devtools.webide.autoInstallADBHelper", false);
|
||||
Services.prefs.clearUserPref("devtools.webide.autoinstallADBHelper");
|
||||
Services.prefs.clearUserPref("devtools.webide.autoinstallFxdtAdapters");
|
||||
});
|
||||
|
||||
function openWebIDE(autoInstallADBHelper) {
|
||||
function openWebIDE(autoInstallAddons) {
|
||||
info("opening WebIDE");
|
||||
|
||||
if (!autoInstallADBHelper) {
|
||||
Services.prefs.setBoolPref("devtools.webide.autoinstallADBHelper", false);
|
||||
}
|
||||
Services.prefs.setBoolPref("devtools.webide.autoinstallADBHelper", !!autoInstallAddons);
|
||||
Services.prefs.setBoolPref("devtools.webide.autoinstallFxdtAdapters", !!autoInstallAddons);
|
||||
|
||||
let deferred = promise.defer();
|
||||
|
||||
|
@ -109,10 +109,10 @@
|
||||
let lis;
|
||||
|
||||
lis = addonDoc.querySelectorAll("li");
|
||||
is(lis.length, 4, "4 addons listed");
|
||||
is(lis.length, 5, "5 addons listed");
|
||||
|
||||
lis = addonDoc.querySelectorAll('li[status="installed"]');
|
||||
is(lis.length, 2, "2 addons installed");
|
||||
is(lis.length, 3, "3 addons installed");
|
||||
|
||||
lis = addonDoc.querySelectorAll('li[status="uninstalled"]');
|
||||
is(lis.length, 2, "2 addons uninstalled");
|
||||
|
@ -6,6 +6,7 @@
|
||||
pref("devtools.webide.showProjectEditor", true);
|
||||
pref("devtools.webide.templatesURL", "https://code.cdn.mozilla.net/templates/list.json");
|
||||
pref("devtools.webide.autoinstallADBHelper", true);
|
||||
pref("devtools.webide.autoinstallFxdtAdapters", false);
|
||||
pref("devtools.webide.restoreLastProject", true);
|
||||
pref("devtools.webide.enableLocalRuntime", true);
|
||||
pref("devtools.webide.addonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/index.json");
|
||||
@ -13,6 +14,8 @@ pref("devtools.webide.simulatorAddonsURL", "https://ftp.mozilla.org/pub/mozilla.
|
||||
pref("devtools.webide.simulatorAddonID", "fxos_#SLASHED_VERSION#_simulator@mozilla.org");
|
||||
pref("devtools.webide.adbAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/adb-helper/#OS#/adbhelper-#OS#-latest.xpi");
|
||||
pref("devtools.webide.adbAddonID", "adbhelper@mozilla.org");
|
||||
pref("devtools.webide.adaptersAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxdt-adapters/#OS#/fxdt-adapters-#OS#-latest.xpi");
|
||||
pref("devtools.webide.adaptersAddonID", "fxdevtools-adapters@mozilla.org");
|
||||
pref("devtools.webide.monitorWebSocketURL", "ws://localhost:9000");
|
||||
pref("devtools.webide.lastConnectedRuntime", "");
|
||||
pref("devtools.webide.lastSelectedProject", "");
|
||||
|
@ -127,7 +127,7 @@ function test() {
|
||||
is(Application.prefs.root, "", "Check the Application preference root");
|
||||
|
||||
// test for user changed preferences
|
||||
ok(Application.prefs.get("browser.shell.checkDefaultBrowser").modified, "A single preference is marked as modified.");
|
||||
ok(Application.prefs.get("browser.dom.window.dump.enabled").modified, "A single preference is marked as modified.");
|
||||
ok(!Application.prefs.get(testdata.string).modified, "A single preference is marked as not modified.");
|
||||
|
||||
// test for a locked preference
|
||||
|
@ -484,6 +484,8 @@ These should match what Safari and other Apple applications use on OS X Lion. --
|
||||
<!ENTITY emailImageCmd.accesskey "g">
|
||||
<!ENTITY emailVideoCmd.label "Email Video…">
|
||||
<!ENTITY emailVideoCmd.accesskey "a">
|
||||
<!ENTITY castVideoCmd.label "Send Video To Device">
|
||||
<!ENTITY castVideoCmd.accesskey "e">
|
||||
<!ENTITY emailAudioCmd.label "Email Audio…">
|
||||
<!ENTITY emailAudioCmd.accesskey "a">
|
||||
<!ENTITY playPluginCmd.label "Activate this plugin">
|
||||
|
@ -100,7 +100,7 @@ quit-button.tooltiptext.mac = Quit %1$S (%2$S)
|
||||
# LOCALIZATION NOTE(loop-call-button3.label): This is a brand name, request
|
||||
# approval before you change it.
|
||||
loop-call-button3.label = Hello
|
||||
loop-call-button2.tooltiptext = Start a conversation
|
||||
loop-call-button3.tooltiptext = Start a conversation
|
||||
|
||||
social-share-button.label = Share This Page
|
||||
social-share-button.tooltiptext = Share This Page
|
||||
|
@ -52,6 +52,7 @@ addons_simulator_label=Firefox OS %1$S Simulator (%2$S)
|
||||
addons_install_button=install
|
||||
addons_uninstall_button=uninstall
|
||||
addons_adb_label=ADB Helper Add-on
|
||||
addons_adapters_label=Tools Adapters Add-on
|
||||
addons_adb_warning=USB devices won't be detected without this add-on
|
||||
addons_status_unknown=?
|
||||
addons_status_installed=Installed
|
||||
|
160
browser/modules/CastingApps.jsm
Normal file
160
browser/modules/CastingApps.jsm
Normal file
@ -0,0 +1,160 @@
|
||||
// -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* 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";
|
||||
this.EXPORTED_SYMBOLS = ["CastingApps"];
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/SimpleServiceDiscovery.jsm");
|
||||
|
||||
|
||||
var CastingApps = {
|
||||
_sendEventToVideo: function (element, data) {
|
||||
let event = element.ownerDocument.createEvent("CustomEvent");
|
||||
event.initCustomEvent("media-videoCasting", false, true, JSON.stringify(data));
|
||||
element.dispatchEvent(event);
|
||||
},
|
||||
|
||||
makeURI: function (url, charset, baseURI) {
|
||||
return Services.io.newURI(url, charset, baseURI);
|
||||
},
|
||||
|
||||
getVideo: function (element) {
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let extensions = SimpleServiceDiscovery.getSupportedExtensions();
|
||||
let types = SimpleServiceDiscovery.getSupportedMimeTypes();
|
||||
|
||||
// Grab the poster attribute from the <video>
|
||||
let posterURL = element.poster;
|
||||
|
||||
// First, look to see if the <video> has a src attribute
|
||||
let sourceURL = element.src;
|
||||
|
||||
// If empty, try the currentSrc
|
||||
if (!sourceURL) {
|
||||
sourceURL = element.currentSrc;
|
||||
}
|
||||
|
||||
if (sourceURL) {
|
||||
// Use the file extension to guess the mime type
|
||||
let sourceURI = this.makeURI(sourceURL, null, this.makeURI(element.baseURI));
|
||||
if (this.allowableExtension(sourceURI, extensions)) {
|
||||
return { element: element, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI};
|
||||
}
|
||||
}
|
||||
|
||||
// Next, look to see if there is a <source> child element that meets
|
||||
// our needs
|
||||
let sourceNodes = element.getElementsByTagName("source");
|
||||
for (let sourceNode of sourceNodes) {
|
||||
let sourceURI = this.makeURI(sourceNode.src, null, this.makeURI(sourceNode.baseURI));
|
||||
|
||||
// Using the type attribute is our ideal way to guess the mime type. Otherwise,
|
||||
// fallback to using the file extension to guess the mime type
|
||||
if (this.allowableMimeType(sourceNode.type, types) || this.allowableExtension(sourceURI, extensions)) {
|
||||
return { element: element, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: sourceNode.type };
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
sendVideoToService: function (videoElement, service) {
|
||||
if (!service)
|
||||
return;
|
||||
|
||||
let video = this.getVideo(videoElement);
|
||||
if (!video) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we have a player app for the given service
|
||||
let app = SimpleServiceDiscovery.findAppForService(service);
|
||||
if (!app)
|
||||
return;
|
||||
|
||||
video.title = videoElement.ownerDocument.defaultView.top.document.title;
|
||||
if (video.element) {
|
||||
// If the video is currently playing on the device, pause it
|
||||
if (!video.element.paused) {
|
||||
video.element.pause();
|
||||
}
|
||||
}
|
||||
|
||||
app.stop(() => {
|
||||
app.start(started => {
|
||||
if (!started) {
|
||||
Cu.reportError("CastingApps: Unable to start app");
|
||||
return;
|
||||
}
|
||||
|
||||
app.remoteMedia(remoteMedia => {
|
||||
if (!remoteMedia) {
|
||||
Cu.reportError("CastingApps: Failed to create remotemedia");
|
||||
return;
|
||||
}
|
||||
|
||||
this.session = {
|
||||
service: service,
|
||||
app: app,
|
||||
remoteMedia: remoteMedia,
|
||||
data: {
|
||||
title: video.title,
|
||||
source: video.source,
|
||||
poster: video.poster
|
||||
},
|
||||
videoRef: Cu.getWeakReference(video.element)
|
||||
};
|
||||
}, this);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getServicesForVideo: function (videoElement) {
|
||||
let video = this.getVideo(videoElement);
|
||||
if (!video) {
|
||||
return {};
|
||||
}
|
||||
|
||||
let filteredServices = SimpleServiceDiscovery.services.filter(service => {
|
||||
return this.allowableExtension(video.sourceURI, service.extensions) ||
|
||||
this.allowableMimeType(video.type, service.types);
|
||||
});
|
||||
|
||||
return filteredServices;
|
||||
},
|
||||
|
||||
// RemoteMedia callback API methods
|
||||
onRemoteMediaStart: function (remoteMedia) {
|
||||
if (!this.session) {
|
||||
return;
|
||||
}
|
||||
|
||||
remoteMedia.load(this.session.data);
|
||||
|
||||
let video = this.session.videoRef.get();
|
||||
if (video) {
|
||||
this._sendEventToVideo(video, { active: true });
|
||||
}
|
||||
},
|
||||
|
||||
onRemoteMediaStop: function (remoteMedia) {
|
||||
},
|
||||
|
||||
onRemoteMediaStatus: function (remoteMedia) {
|
||||
},
|
||||
|
||||
allowableExtension: function (uri, extensions) {
|
||||
return (uri instanceof Ci.nsIURL) && extensions.indexOf(uri.fileExtension) != -1;
|
||||
},
|
||||
|
||||
allowableMimeType: function (type, types) {
|
||||
return types.indexOf(type) != -1;
|
||||
}
|
||||
};
|
@ -14,6 +14,7 @@ XPCSHELL_TESTS_MANIFESTS += [
|
||||
EXTRA_JS_MODULES += [
|
||||
'BrowserNewTabPreloader.jsm',
|
||||
'BrowserUITelemetry.jsm',
|
||||
'CastingApps.jsm',
|
||||
'Chat.jsm',
|
||||
'ContentClick.jsm',
|
||||
'ContentLinkHandler.jsm',
|
||||
|
@ -165,9 +165,11 @@
|
||||
}
|
||||
|
||||
/* Vertical toolbar border */
|
||||
#main-window[sizemode=normal] #navigator-toolbox:not(:-moz-lwtheme)::after,
|
||||
#main-window[sizemode=normal] #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(:-moz-lwtheme),
|
||||
#main-window[sizemode=normal] #navigator-toolbox:-moz-lwtheme {
|
||||
#main-window:not([customizing])[sizemode=normal] #navigator-toolbox:not(:-moz-lwtheme)::after,
|
||||
#main-window:not([customizing])[sizemode=normal] #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(:-moz-lwtheme),
|
||||
#main-window:not([customizing])[sizemode=normal] #navigator-toolbox:-moz-lwtheme,
|
||||
#main-window[customizing] #navigator-toolbox::after,
|
||||
#main-window[customizing] #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar) {
|
||||
border-left: 1px solid @toolbarShadowColor@;
|
||||
border-right: 1px solid @toolbarShadowColor@;
|
||||
background-clip: padding-box;
|
||||
|
@ -2834,10 +2834,10 @@ nsDocShell::SetRecordProfileTimelineMarkers(bool aValue)
|
||||
if (currentValue != aValue) {
|
||||
if (aValue) {
|
||||
++gProfileTimelineRecordingsCount;
|
||||
mProfileTimelineStartTime = TimeStamp::Now();
|
||||
mProfileTimelineRecording = true;
|
||||
} else {
|
||||
--gProfileTimelineRecordingsCount;
|
||||
mProfileTimelineStartTime = TimeStamp();
|
||||
mProfileTimelineRecording = false;
|
||||
ClearProfileTimelineMarkers();
|
||||
}
|
||||
}
|
||||
@ -2851,7 +2851,7 @@ nsDocShell::SetRecordProfileTimelineMarkers(bool aValue)
|
||||
NS_IMETHODIMP
|
||||
nsDocShell::GetRecordProfileTimelineMarkers(bool* aValue)
|
||||
{
|
||||
*aValue = !mProfileTimelineStartTime.IsNull();
|
||||
*aValue = mProfileTimelineRecording;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -2939,10 +2939,12 @@ nsDocShell::PopProfileTimelineMarkers(JSContext* aCx,
|
||||
#endif
|
||||
}
|
||||
|
||||
float
|
||||
nsDocShell::GetProfileTimelineDelta()
|
||||
nsresult
|
||||
nsDocShell::Now(DOMHighResTimeStamp* aWhen)
|
||||
{
|
||||
return (TimeStamp::Now() - mProfileTimelineStartTime).ToMilliseconds();
|
||||
bool ignore;
|
||||
*aWhen = (TimeStamp::Now() - TimeStamp::ProcessCreation(ignore)).ToMilliseconds();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
@ -2950,8 +2952,9 @@ nsDocShell::AddProfileTimelineMarker(const char* aName,
|
||||
TracingMetadata aMetaData)
|
||||
{
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
if (!mProfileTimelineStartTime.IsNull()) {
|
||||
float delta = GetProfileTimelineDelta();
|
||||
if (mProfileTimelineRecording) {
|
||||
DOMHighResTimeStamp delta;
|
||||
Now(&delta);
|
||||
ProfilerMarkerTracing* payload = new ProfilerMarkerTracing("Timeline",
|
||||
aMetaData);
|
||||
mProfileTimelineMarkers.AppendElement(
|
||||
@ -2966,8 +2969,9 @@ nsDocShell::AddProfileTimelineMarker(const char* aName,
|
||||
TracingMetadata aMetaData)
|
||||
{
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
if (!mProfileTimelineStartTime.IsNull()) {
|
||||
float delta = GetProfileTimelineDelta();
|
||||
if (mProfileTimelineRecording) {
|
||||
DOMHighResTimeStamp delta;
|
||||
Now(&delta);
|
||||
ProfilerMarkerTracing* payload = new ProfilerMarkerTracing("Timeline",
|
||||
aMetaData,
|
||||
aCause);
|
||||
|
@ -951,15 +951,15 @@ private:
|
||||
nsWeakPtr mOpener;
|
||||
nsWeakPtr mOpenedRemote;
|
||||
|
||||
// Storing profile timeline markers and if/when recording started
|
||||
mozilla::TimeStamp mProfileTimelineStartTime;
|
||||
// True if recording profiles.
|
||||
bool mProfileTimelineRecording;
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
struct InternalProfileTimelineMarker
|
||||
{
|
||||
InternalProfileTimelineMarker(const char* aName,
|
||||
ProfilerMarkerTracing* aPayload,
|
||||
float aTime)
|
||||
DOMHighResTimeStamp aTime)
|
||||
: mName(aName)
|
||||
, mPayload(aPayload)
|
||||
, mTime(aTime)
|
||||
@ -972,15 +972,11 @@ private:
|
||||
|
||||
const char* mName;
|
||||
ProfilerMarkerTracing* mPayload;
|
||||
float mTime;
|
||||
DOMHighResTimeStamp mTime;
|
||||
};
|
||||
nsTArray<InternalProfileTimelineMarker*> mProfileTimelineMarkers;
|
||||
#endif
|
||||
|
||||
// Get the elapsed time (in millis) since the profile timeline recording
|
||||
// started
|
||||
float GetProfileTimelineDelta();
|
||||
|
||||
// Get rid of all the timeline markers accumulated so far
|
||||
void ClearProfileTimelineMarkers();
|
||||
|
||||
|
@ -54,7 +54,7 @@ interface nsITabParent;
|
||||
|
||||
typedef unsigned long nsLoadFlags;
|
||||
|
||||
[scriptable, builtinclass, uuid(23157a63-26fd-44a0-a0f9-fdc64dcc004c)]
|
||||
[scriptable, builtinclass, uuid(da8f78f1-8f20-4d6d-be56-fe53e177b630)]
|
||||
interface nsIDocShell : nsIDocShellTreeItem
|
||||
{
|
||||
/**
|
||||
@ -672,6 +672,14 @@ interface nsIDocShell : nsIDocShellTreeItem
|
||||
*/
|
||||
[infallible] attribute boolean recordProfileTimelineMarkers;
|
||||
|
||||
/**
|
||||
* Return a DOMHighResTimeStamp representing the number of
|
||||
* milliseconds from an arbitrary point in time. The reference
|
||||
* point is shared by all DocShells and is also used by timestamps
|
||||
* on markers.
|
||||
*/
|
||||
DOMHighResTimeStamp now();
|
||||
|
||||
/**
|
||||
* Returns and flushes the profile timeline markers gathered by the docShell
|
||||
*/
|
||||
|
@ -6,6 +6,6 @@
|
||||
|
||||
dictionary ProfileTimelineMarker {
|
||||
DOMString name = "";
|
||||
DOMTimeStamp start = 0;
|
||||
DOMTimeStamp end = 0;
|
||||
DOMHighResTimeStamp start = 0;
|
||||
DOMHighResTimeStamp end = 0;
|
||||
};
|
||||
|
@ -21,10 +21,10 @@ import org.mozilla.gecko.AppConstants.Versions;
|
||||
import org.mozilla.gecko.DynamicToolbar.PinReason;
|
||||
import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
|
||||
import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
|
||||
import org.mozilla.gecko.ReadingListHelper;
|
||||
import org.mozilla.gecko.animation.PropertyAnimator;
|
||||
import org.mozilla.gecko.animation.ViewHelper;
|
||||
import org.mozilla.gecko.db.BrowserContract.Combined;
|
||||
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
|
||||
import org.mozilla.gecko.db.BrowserContract.SearchHistory;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.SuggestedSites;
|
||||
@ -146,10 +146,6 @@ public class BrowserApp extends GeckoApp
|
||||
|
||||
private static final int TABS_ANIMATION_DURATION = 450;
|
||||
|
||||
private static final int READER_ADD_SUCCESS = 0;
|
||||
private static final int READER_ADD_FAILED = 1;
|
||||
private static final int READER_ADD_DUPLICATE = 2;
|
||||
|
||||
private static final String ADD_SHORTCUT_TOAST = "add_shortcut_toast";
|
||||
public static final String GUEST_BROWSING_ARG = "--guest";
|
||||
|
||||
@ -229,6 +225,8 @@ public class BrowserApp extends GeckoApp
|
||||
|
||||
private BrowserHealthReporter mBrowserHealthReporter;
|
||||
|
||||
private ReadingListHelper mReadingListHelper;
|
||||
|
||||
private SystemBarTintManager mTintManager;
|
||||
|
||||
// The tab to be selected on editing mode exit.
|
||||
@ -433,90 +431,6 @@ public class BrowserApp extends GeckoApp
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
void handleReaderListStatusRequest(final String url) {
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final int inReadingList = BrowserDB.isReadingListItem(getContentResolver(), url) ? 1 : 0;
|
||||
|
||||
final JSONObject json = new JSONObject();
|
||||
try {
|
||||
json.put("url", url);
|
||||
json.put("inReadingList", inReadingList);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "JSON error - failed to return inReadingList status", e);
|
||||
return;
|
||||
}
|
||||
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:ListStatusReturn", json.toString()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleReaderAdded(int result, final ContentValues values) {
|
||||
if (result != READER_ADD_SUCCESS) {
|
||||
if (result == READER_ADD_FAILED) {
|
||||
showToast(R.string.reading_list_failed, Toast.LENGTH_SHORT);
|
||||
} else if (result == READER_ADD_DUPLICATE) {
|
||||
showToast(R.string.reading_list_duplicate, Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
BrowserDB.addReadingListItem(getContentResolver(), values);
|
||||
showToast(R.string.reading_list_added, Toast.LENGTH_SHORT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ContentValues messageToReadingListContentValues(JSONObject message) {
|
||||
final ContentValues values = new ContentValues();
|
||||
values.put(ReadingListItems.URL, message.optString("url"));
|
||||
values.put(ReadingListItems.TITLE, message.optString("title"));
|
||||
values.put(ReadingListItems.LENGTH, message.optInt("length"));
|
||||
values.put(ReadingListItems.EXCERPT, message.optString("excerpt"));
|
||||
return values;
|
||||
}
|
||||
|
||||
void handleReaderRemoved(final String url) {
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
BrowserDB.removeReadingListItemWithURL(getContentResolver(), url);
|
||||
showToast(R.string.page_removed, Toast.LENGTH_SHORT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleReaderFaviconRequest(final String url) {
|
||||
(new UIAsyncTask.WithoutParams<String>(ThreadUtils.getBackgroundHandler()) {
|
||||
@Override
|
||||
public String doInBackground() {
|
||||
return Favicons.getFaviconURLForPageURL(getContext(), url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute(String faviconUrl) {
|
||||
JSONObject args = new JSONObject();
|
||||
|
||||
if (faviconUrl != null) {
|
||||
try {
|
||||
args.put("url", url);
|
||||
args.put("faviconUrl", faviconUrl);
|
||||
} catch (JSONException e) {
|
||||
Log.w(LOGTAG, "Error building JSON favicon arguments.", e);
|
||||
}
|
||||
}
|
||||
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:FaviconReturn", args.toString()));
|
||||
}
|
||||
}).execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
mAboutHomeStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_ABOUTHOME");
|
||||
@ -600,8 +514,6 @@ public class BrowserApp extends GeckoApp
|
||||
|
||||
EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener)this,
|
||||
"Menu:Update",
|
||||
"Reader:Added",
|
||||
"Reader:FaviconRequest",
|
||||
"Search:Keyword",
|
||||
"Prompt:ShowTop",
|
||||
"Accounts:Exist");
|
||||
@ -615,8 +527,6 @@ public class BrowserApp extends GeckoApp
|
||||
"Feedback:OpenPlayStore",
|
||||
"Menu:Add",
|
||||
"Menu:Remove",
|
||||
"Reader:ListStatusRequest",
|
||||
"Reader:Removed",
|
||||
"Reader:Share",
|
||||
"Settings:Show",
|
||||
"Telemetry:Gather",
|
||||
@ -633,6 +543,7 @@ public class BrowserApp extends GeckoApp
|
||||
mSharedPreferencesHelper = new SharedPreferencesHelper(appContext);
|
||||
mOrderedBroadcastHelper = new OrderedBroadcastHelper(appContext);
|
||||
mBrowserHealthReporter = new BrowserHealthReporter();
|
||||
mReadingListHelper = new ReadingListHelper(appContext);
|
||||
|
||||
if (AppConstants.MOZ_ANDROID_BEAM) {
|
||||
NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
|
||||
@ -1142,10 +1053,13 @@ public class BrowserApp extends GeckoApp
|
||||
mBrowserHealthReporter = null;
|
||||
}
|
||||
|
||||
if (mReadingListHelper != null) {
|
||||
mReadingListHelper.uninit();
|
||||
mReadingListHelper = null;
|
||||
}
|
||||
|
||||
EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener)this,
|
||||
"Menu:Update",
|
||||
"Reader:Added",
|
||||
"Reader:FaviconRequest",
|
||||
"Search:Keyword",
|
||||
"Prompt:ShowTop",
|
||||
"Accounts:Exist");
|
||||
@ -1159,8 +1073,6 @@ public class BrowserApp extends GeckoApp
|
||||
"Feedback:OpenPlayStore",
|
||||
"Menu:Add",
|
||||
"Menu:Remove",
|
||||
"Reader:ListStatusRequest",
|
||||
"Reader:Removed",
|
||||
"Reader:Share",
|
||||
"Settings:Show",
|
||||
"Telemetry:Gather",
|
||||
@ -1519,13 +1431,6 @@ public class BrowserApp extends GeckoApp
|
||||
}
|
||||
});
|
||||
|
||||
} else if ("Reader:ListStatusRequest".equals(event)) {
|
||||
handleReaderListStatusRequest(message.getString("url"));
|
||||
|
||||
} else if ("Reader:Removed".equals(event)) {
|
||||
final String url = message.getString("url");
|
||||
handleReaderRemoved(url);
|
||||
|
||||
} else if ("Reader:Share".equals(event)) {
|
||||
final String title = message.getString("title");
|
||||
final String url = message.getString("url");
|
||||
@ -1640,12 +1545,6 @@ public class BrowserApp extends GeckoApp
|
||||
DataReportingNotification.checkAndNotifyPolicy(GeckoAppShell.getContext());
|
||||
}
|
||||
|
||||
} else if (event.equals("Reader:Added")) {
|
||||
final int result = message.getInt("result");
|
||||
handleReaderAdded(result, messageToReadingListContentValues(message));
|
||||
} else if (event.equals("Reader:FaviconRequest")) {
|
||||
final String url = message.getString("url");
|
||||
handleReaderFaviconRequest(url);
|
||||
} else if (event.equals("Search:Keyword")) {
|
||||
storeSearchQuery(message.getString("query"));
|
||||
} else if (event.equals("Prompt:ShowTop")) {
|
||||
|
@ -1835,7 +1835,7 @@ public class GeckoAppShell
|
||||
true, // Media scanner should scan this
|
||||
mimeType,
|
||||
f.getAbsolutePath(),
|
||||
Math.max(0, f.length()),
|
||||
Math.max(1, f.length()), // Some versions of Android require downloads to be at least length 1
|
||||
false); // Don't show a notification.
|
||||
} else {
|
||||
Context context = getContext();
|
||||
|
195
mobile/android/base/ReadingListHelper.java
Normal file
195
mobile/android/base/ReadingListHelper.java
Normal file
@ -0,0 +1,195 @@
|
||||
/* 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/. */
|
||||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.favicons.Favicons;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.NativeEventListener;
|
||||
import org.mozilla.gecko.util.NativeJSObject;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.util.UIAsyncTask;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
public final class ReadingListHelper implements GeckoEventListener, NativeEventListener {
|
||||
private static final String LOGTAG = "ReadingListHelper";
|
||||
|
||||
private static final int READER_ADD_SUCCESS = 0;
|
||||
private static final int READER_ADD_FAILED = 1;
|
||||
private static final int READER_ADD_DUPLICATE = 2;
|
||||
|
||||
protected final Context context;
|
||||
|
||||
|
||||
public ReadingListHelper(Context context) {
|
||||
this.context = context;
|
||||
|
||||
EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener) this,
|
||||
"Reader:Added", "Reader:FaviconRequest");
|
||||
EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener) this,
|
||||
"Reader:ListStatusRequest", "Reader:Removed");
|
||||
}
|
||||
|
||||
public void uninit() {
|
||||
EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener) this,
|
||||
"Reader:Added", "Reader:FaviconRequest");
|
||||
EventDispatcher.getInstance().unregisterGeckoThreadListener((NativeEventListener) this,
|
||||
"Reader:ListStatusRequest", "Reader:Removed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(String event, JSONObject message) {
|
||||
switch(event) {
|
||||
case "Reader:Added": {
|
||||
handleReadingListAdded(message);
|
||||
break;
|
||||
}
|
||||
|
||||
case "Reader:FaviconRequest": {
|
||||
handleReaderModeFaviconRequest(message.optString("url"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(final String event, final NativeJSObject message,
|
||||
final EventCallback callback) {
|
||||
switch(event) {
|
||||
case "Reader:Removed": {
|
||||
handleReadingListRemoved(message.getString("url"));
|
||||
break;
|
||||
}
|
||||
|
||||
case "Reader:ListStatusRequest": {
|
||||
handleReadingListStatusRequest(message.getString("url"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A page can be added to the ReadingList by long-tap of the page-action
|
||||
* icon, or by tapping the readinglist-add icon in the ReaderMode banner.
|
||||
*/
|
||||
private void handleReadingListAdded(JSONObject message) {
|
||||
final int result = message.optInt("result", READER_ADD_FAILED);
|
||||
if (result != READER_ADD_SUCCESS) {
|
||||
if (result == READER_ADD_FAILED) {
|
||||
showToast(R.string.reading_list_failed, Toast.LENGTH_SHORT);
|
||||
} else if (result == READER_ADD_DUPLICATE) {
|
||||
showToast(R.string.reading_list_duplicate, Toast.LENGTH_SHORT);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final ContentValues values = new ContentValues();
|
||||
values.put(ReadingListItems.URL, message.optString("url"));
|
||||
values.put(ReadingListItems.TITLE, message.optString("title"));
|
||||
values.put(ReadingListItems.LENGTH, message.optInt("length"));
|
||||
values.put(ReadingListItems.EXCERPT, message.optString("excerpt"));
|
||||
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
BrowserDB.addReadingListItem(context.getContentResolver(), values);
|
||||
showToast(R.string.reading_list_added, Toast.LENGTH_SHORT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gecko (ReaderMode) requests the page favicon to append to the
|
||||
* document head for display.
|
||||
*/
|
||||
private void handleReaderModeFaviconRequest(final String url) {
|
||||
(new UIAsyncTask.WithoutParams<String>(ThreadUtils.getBackgroundHandler()) {
|
||||
@Override
|
||||
public String doInBackground() {
|
||||
return Favicons.getFaviconURLForPageURL(context, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute(String faviconUrl) {
|
||||
JSONObject args = new JSONObject();
|
||||
|
||||
if (faviconUrl != null) {
|
||||
try {
|
||||
args.put("url", url);
|
||||
args.put("faviconUrl", faviconUrl);
|
||||
} catch (JSONException e) {
|
||||
Log.w(LOGTAG, "Error building JSON favicon arguments.", e);
|
||||
}
|
||||
}
|
||||
|
||||
GeckoAppShell.sendEventToGecko(
|
||||
GeckoEvent.createBroadcastEvent("Reader:FaviconReturn", args.toString()));
|
||||
}
|
||||
}).execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* A page can be removed from the ReadingList by panel context menu,
|
||||
* or by tapping the readinglist-remove icon in the ReaderMode banner.
|
||||
*/
|
||||
private void handleReadingListRemoved(final String url) {
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
BrowserDB.removeReadingListItemWithURL(context.getContentResolver(), url);
|
||||
showToast(R.string.page_removed, Toast.LENGTH_SHORT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gecko (ReaderMode) requests the page ReadingList status, to display
|
||||
* the proper ReaderMode banner icon (readinglist-add / readinglist-remove).
|
||||
*/
|
||||
private void handleReadingListStatusRequest(final String url) {
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final int inReadingList =
|
||||
BrowserDB.isReadingListItem(context.getContentResolver(), url) ? 1 : 0;
|
||||
|
||||
final JSONObject json = new JSONObject();
|
||||
try {
|
||||
json.put("url", url);
|
||||
json.put("inReadingList", inReadingList);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "JSON error - failed to return inReadingList status", e);
|
||||
return;
|
||||
}
|
||||
|
||||
GeckoAppShell.sendEventToGecko(
|
||||
GeckoEvent.createBroadcastEvent("Reader:ListStatusReturn", json.toString()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show various status toasts.
|
||||
*/
|
||||
private void showToast(final int resId, final int duration) {
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(context, resId, duration).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -82,7 +82,7 @@ public class LocalBrowserDB {
|
||||
private final HashMap<String, Long> mFolderIdMap;
|
||||
|
||||
// Use wrapped Boolean so that we can have a null state
|
||||
private Boolean mDesktopBookmarksExist;
|
||||
private volatile Boolean mDesktopBookmarksExist;
|
||||
|
||||
private final Uri mBookmarksUriWithProfile;
|
||||
private final Uri mParentsUriWithProfile;
|
||||
@ -759,12 +759,13 @@ public class LocalBrowserDB {
|
||||
null);
|
||||
|
||||
try {
|
||||
mDesktopBookmarksExist = c.getCount() > 0;
|
||||
// Don't read back out of the cache to avoid races with invalidation.
|
||||
final boolean e = c.getCount() > 0;
|
||||
mDesktopBookmarksExist = e;
|
||||
return e;
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
|
||||
return mDesktopBookmarksExist;
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
|
@ -6,13 +6,14 @@ package org.mozilla.gecko.menu;
|
||||
|
||||
import org.mozilla.gecko.NewTabletUI;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.widget.ThemedImageButton;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
public class MenuItemActionBar extends ImageButton
|
||||
public class MenuItemActionBar extends ThemedImageButton
|
||||
implements GeckoMenuItem.Layout {
|
||||
private static final String LOGTAG = "GeckoMenuItemActionBar";
|
||||
|
||||
|
@ -368,6 +368,7 @@ gbjar.sources += [
|
||||
'prompts/PromptService.java',
|
||||
'prompts/TabInput.java',
|
||||
'ReaderModeUtils.java',
|
||||
'ReadingListHelper.java',
|
||||
'RemoteClientsDialogFragment.java',
|
||||
'RemoteTabsExpandableListAdapter.java',
|
||||
'Restarter.java',
|
||||
|
@ -3,7 +3,40 @@
|
||||
- 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/. -->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:gecko="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item gecko:state_private="true"
|
||||
android:state_pressed="true"
|
||||
android:state_enabled="true">
|
||||
|
||||
<inset android:insetTop="@dimen/new_tablet_browser_toolbar_menu_item_inset_vertical"
|
||||
android:insetBottom="@dimen/new_tablet_browser_toolbar_menu_item_inset_vertical"
|
||||
android:insetLeft="@dimen/new_tablet_browser_toolbar_menu_item_inset_horizontal"
|
||||
android:insetRight="@dimen/new_tablet_browser_toolbar_menu_item_inset_horizontal">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/new_tablet_highlight_pb"/>
|
||||
<corners android:radius="@dimen/new_tablet_browser_toolbar_menu_item_corner_radius"/>
|
||||
</shape>
|
||||
</inset>
|
||||
|
||||
</item>
|
||||
|
||||
<item gecko:state_private="true"
|
||||
android:state_focused="true"
|
||||
android:state_pressed="false">
|
||||
|
||||
<inset android:insetTop="@dimen/new_tablet_browser_toolbar_menu_item_inset_vertical"
|
||||
android:insetBottom="@dimen/new_tablet_browser_toolbar_menu_item_inset_vertical"
|
||||
android:insetLeft="@dimen/new_tablet_browser_toolbar_menu_item_inset_horizontal"
|
||||
android:insetRight="@dimen/new_tablet_browser_toolbar_menu_item_inset_horizontal">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/new_tablet_highlight_focused_pb"/>
|
||||
<corners android:radius="@dimen/new_tablet_browser_toolbar_menu_item_corner_radius"/>
|
||||
</shape>
|
||||
</inset>
|
||||
|
||||
</item>
|
||||
|
||||
<item android:state_pressed="true"
|
||||
android:state_enabled="true">
|
||||
|
@ -11,7 +11,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignLeft="@+id/back"
|
||||
android:layout_toLeftOf="@id/menu_items"
|
||||
android:layout_marginLeft="@dimen/back_button_width_half"
|
||||
android:layout_marginLeft="@dimen/new_tablet_nav_button_width_half"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:duplicateParentState="true"
|
||||
@ -25,8 +25,8 @@
|
||||
|
||||
<org.mozilla.gecko.toolbar.BackButton android:id="@id/back"
|
||||
style="@style/UrlBar.ImageButton.NewTablet"
|
||||
android:layout_width="@dimen/back_button_width"
|
||||
android:layout_height="@dimen/back_button_width"
|
||||
android:layout_width="@dimen/new_tablet_nav_button_width"
|
||||
android:layout_height="@dimen/new_tablet_nav_button_width"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_alignParentLeft="true"
|
||||
@ -43,8 +43,7 @@
|
||||
android:layout_toRightOf="@id/back"
|
||||
android:layout_toLeftOf="@id/menu_items"/>
|
||||
|
||||
<!-- Note: * Values of marginLeft are used to animate the forward button so don't change its value.
|
||||
* We set the padding on the site security icon to increase its tappable area. -->
|
||||
<!-- Note: we set the padding on the site security icon to increase its tappable area. -->
|
||||
<org.mozilla.gecko.toolbar.ToolbarDisplayLayout android:id="@+id/display_layout"
|
||||
style="@style/UrlBar.Button.Container"
|
||||
android:layout_toRightOf="@id/back"
|
||||
|
@ -0,0 +1,7 @@
|
||||
<?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/. -->
|
||||
|
||||
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:src="@null"/>
|
@ -8,10 +8,6 @@
|
||||
<dimen name="browser_toolbar_height">56dp</dimen>
|
||||
<dimen name="browser_toolbar_button_padding">16dp</dimen>
|
||||
|
||||
<!-- If you update one of these values, update the other. -->
|
||||
<dimen name="back_button_width">42dp</dimen>
|
||||
<dimen name="back_button_width_half">21dp</dimen>
|
||||
|
||||
<dimen name="tabs_counter_size">26sp</dimen>
|
||||
<dimen name="panel_grid_view_column_width">200dp</dimen>
|
||||
|
||||
|
@ -25,8 +25,9 @@
|
||||
<item name="android:layout_marginLeft">@dimen/forward_default_offset</item>
|
||||
</style>
|
||||
|
||||
<!-- Note: this style is for the visible and expanded forward button. We translate/hide
|
||||
the forward button in code - see BrowserToolbarNewTablet.animateForwardButton. -->
|
||||
<style name="UrlBar.ImageButton.Forward.NewTablet">
|
||||
<item name="android:layout_marginLeft">@dimen/new_tablet_forward_default_offset</item>
|
||||
<item name="android:layout_height">match_parent</item>
|
||||
<item name="android:paddingTop">0dp</item>
|
||||
<item name="android:paddingBottom">0dp</item>
|
||||
@ -34,6 +35,20 @@
|
||||
<item name="android:layout_marginBottom">11.5dp</item>
|
||||
<item name="android:src">@drawable/new_tablet_ic_menu_forward</item>
|
||||
<item name="android:background">@drawable/new_tablet_url_bar_nav_button</item>
|
||||
|
||||
<!-- The visible area of the forward button is a nav_button_width and the
|
||||
non-visible area slides halfway under the back button. This non-visible
|
||||
area is used to ensure the forward button background fully
|
||||
covers the space to the right of the back button. -->
|
||||
<item name="android:layout_width">@dimen/new_tablet_nav_button_width_plus_half</item>
|
||||
|
||||
<!-- (See note above) We left align with back,
|
||||
but only need to hide halfway underneath. -->
|
||||
<item name="android:layout_marginLeft">@dimen/new_tablet_nav_button_width_half</item>
|
||||
|
||||
<!-- We use left padding to center the arrow in the
|
||||
visible area as opposed to the true width. -->
|
||||
<item name="android:paddingLeft">18dp</item>
|
||||
</style>
|
||||
|
||||
<style name="UrlBar.ImageButton.TabCount.NewTablet">
|
||||
|
@ -17,11 +17,15 @@
|
||||
<dimen name="browser_toolbar_favicon_size">21.33dip</dimen>
|
||||
<dimen name="browser_toolbar_shadow_size">2dp</dimen>
|
||||
|
||||
<!-- If you update one of these values, update the others. -->
|
||||
<dimen name="new_tablet_nav_button_width">42dp</dimen>
|
||||
<dimen name="new_tablet_nav_button_width_half">21dp</dimen>
|
||||
<dimen name="new_tablet_nav_button_width_plus_half">63dp</dimen>
|
||||
|
||||
<dimen name="new_tablet_tab_strip_height">48dp</dimen>
|
||||
<dimen name="new_tablet_tab_strip_item_width">250dp</dimen>
|
||||
<dimen name="new_tablet_tab_strip_item_margin">-30dp</dimen>
|
||||
<dimen name="new_tablet_tab_strip_favicon_size">16dp</dimen>
|
||||
<dimen name="new_tablet_forward_default_offset">-6dp</dimen>
|
||||
<dimen name="new_tablet_site_security_height">60dp</dimen>
|
||||
<dimen name="new_tablet_site_security_width">34dp</dimen>
|
||||
<!-- We primarily use padding (instead of margins) to increase the hit area. -->
|
||||
|
@ -10,7 +10,6 @@ import org.mozilla.gecko.animation.PropertyAnimator;
|
||||
import org.mozilla.gecko.animation.ViewHelper;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
/**
|
||||
@ -21,15 +20,18 @@ class BrowserToolbarNewTablet extends BrowserToolbarTabletBase {
|
||||
|
||||
private static final int FORWARD_ANIMATION_DURATION = 450;
|
||||
|
||||
private final int urlBarViewOffset;
|
||||
private final int defaultForwardMargin;
|
||||
private final int forwardButtonTranslationWidth;
|
||||
|
||||
public BrowserToolbarNewTablet(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
final Resources res = getResources();
|
||||
urlBarViewOffset = res.getDimensionPixelSize(R.dimen.url_bar_offset_left);
|
||||
defaultForwardMargin = res.getDimensionPixelSize(R.dimen.new_tablet_forward_default_offset);
|
||||
forwardButtonTranslationWidth =
|
||||
getResources().getDimensionPixelOffset(R.dimen.new_tablet_nav_button_width);
|
||||
|
||||
// The forward button is initially expanded (in the layout file)
|
||||
// so translate it for start of the expansion animation; future
|
||||
// iterations translate it to this position when hiding and will already be set up.
|
||||
ViewHelper.setTranslationX(forwardButton, -forwardButtonTranslationWidth);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -49,25 +51,24 @@ class BrowserToolbarNewTablet extends BrowserToolbarTabletBase {
|
||||
|
||||
@Override
|
||||
protected void animateForwardButton(final ForwardButtonAnimation animation) {
|
||||
final boolean showing = (animation == ForwardButtonAnimation.SHOW);
|
||||
final boolean willShowForward = (animation == ForwardButtonAnimation.SHOW);
|
||||
|
||||
// if the forward button's margin is non-zero, this means it has already
|
||||
// been animated to be visible¸ and vice-versa.
|
||||
MarginLayoutParams fwdParams = (MarginLayoutParams) forwardButton.getLayoutParams();
|
||||
if ((fwdParams.leftMargin > defaultForwardMargin && showing) ||
|
||||
(fwdParams.leftMargin == defaultForwardMargin && !showing)) {
|
||||
// If we're not in the appropriate state to start a particular animation,
|
||||
// then we must be in the opposite state and do not need to animate.
|
||||
final float forwardOffset = ViewHelper.getTranslationX(forwardButton);
|
||||
if ((forwardOffset >= 0 && willShowForward) ||
|
||||
forwardOffset < 0 && !willShowForward) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We want the forward button to show immediately when switching tabs
|
||||
final PropertyAnimator forwardAnim =
|
||||
new PropertyAnimator(isSwitchingTabs ? 10 : FORWARD_ANIMATION_DURATION);
|
||||
final int width = Math.round(forwardButton.getWidth() * .75f);
|
||||
|
||||
forwardAnim.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
|
||||
@Override
|
||||
public void onPropertyAnimationStart() {
|
||||
if (!showing) {
|
||||
if (!willShowForward) {
|
||||
// Set the margin before the transition when hiding the forward button. We
|
||||
// have to do this so that the favicon isn't clipped during the transition
|
||||
MarginLayoutParams layoutParams =
|
||||
@ -87,26 +88,23 @@ class BrowserToolbarNewTablet extends BrowserToolbarTabletBase {
|
||||
|
||||
@Override
|
||||
public void onPropertyAnimationEnd() {
|
||||
if (showing) {
|
||||
if (willShowForward) {
|
||||
// Increase the margins to ensure the text does not run outside the View.
|
||||
MarginLayoutParams layoutParams =
|
||||
(MarginLayoutParams) urlDisplayLayout.getLayoutParams();
|
||||
layoutParams.leftMargin = urlBarViewOffset;
|
||||
layoutParams.leftMargin = forwardButtonTranslationWidth;
|
||||
|
||||
layoutParams = (MarginLayoutParams) urlEditLayout.getLayoutParams();
|
||||
layoutParams.leftMargin = urlBarViewOffset;
|
||||
layoutParams.leftMargin = forwardButtonTranslationWidth;
|
||||
}
|
||||
|
||||
urlDisplayLayout.finishForwardAnimation();
|
||||
|
||||
MarginLayoutParams layoutParams = (MarginLayoutParams) forwardButton.getLayoutParams();
|
||||
layoutParams.leftMargin = defaultForwardMargin + (showing ? width : 0);
|
||||
ViewHelper.setTranslationX(forwardButton, 0);
|
||||
|
||||
requestLayout();
|
||||
}
|
||||
});
|
||||
|
||||
prepareForwardAnimation(forwardAnim, animation, width);
|
||||
prepareForwardAnimation(forwardAnim, animation, forwardButtonTranslationWidth);
|
||||
forwardAnim.start();
|
||||
}
|
||||
|
||||
@ -122,7 +120,7 @@ class BrowserToolbarNewTablet extends BrowserToolbarTabletBase {
|
||||
} else {
|
||||
anim.attach(forwardButton,
|
||||
PropertyAnimator.Property.TRANSLATION_X,
|
||||
width);
|
||||
0);
|
||||
anim.attach(forwardButton,
|
||||
PropertyAnimator.Property.ALPHA,
|
||||
1);
|
||||
|
@ -10,6 +10,7 @@ import java.util.Arrays;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.menu.MenuItemActionBar;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
@ -121,8 +122,13 @@ abstract class BrowserToolbarTabletBase extends BrowserToolbar {
|
||||
@Override
|
||||
public void setPrivateMode(final boolean isPrivate) {
|
||||
super.setPrivateMode(isPrivate);
|
||||
|
||||
backButton.setPrivateMode(isPrivate);
|
||||
forwardButton.setPrivateMode(isPrivate);
|
||||
for (int i = 0; i < actionItemBar.getChildCount(); ++i) {
|
||||
final MenuItemActionBar child = (MenuItemActionBar) actionItemBar.getChildAt(i);
|
||||
child.setPrivateMode(isPrivate);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean canDoBack(final Tab tab) {
|
||||
|
@ -535,6 +535,8 @@ public class ToolbarDisplayLayout extends ThemedLinearLayout
|
||||
mForwardAnim = anim;
|
||||
|
||||
if (animation == ForwardButtonAnimation.HIDE) {
|
||||
// We animate these items individually, rather than this entire view,
|
||||
// so that we don't animate certain views, e.g. the stop button.
|
||||
anim.attach(mTitle,
|
||||
PropertyAnimator.Property.TRANSLATION_X,
|
||||
0);
|
||||
|
@ -1,4 +1,5 @@
|
||||
//#filter substitution
|
||||
//#define VIEW_NAME_SUFFIX EditText
|
||||
//#define BASE_TYPE android.widget.EditText
|
||||
//#define STYLE_CONSTRUCTOR 1
|
||||
//#include ThemedView.java.frag
|
||||
|
@ -1,4 +1,5 @@
|
||||
//#filter substitution
|
||||
//#define VIEW_NAME_SUFFIX ImageButton
|
||||
//#define BASE_TYPE android.widget.ImageButton
|
||||
//#define STYLE_CONSTRUCTOR 1
|
||||
//#include ThemedView.java.frag
|
||||
|
@ -1,4 +1,5 @@
|
||||
//#filter substitution
|
||||
//#define VIEW_NAME_SUFFIX ImageView
|
||||
//#define BASE_TYPE android.widget.ImageView
|
||||
//#define STYLE_CONSTRUCTOR 1
|
||||
//#include ThemedView.java.frag
|
||||
|
@ -1,4 +1,5 @@
|
||||
//#filter substitution
|
||||
//#define VIEW_NAME_SUFFIX RelativeLayout
|
||||
//#define BASE_TYPE android.widget.RelativeLayout
|
||||
//#define STYLE_CONSTRUCTOR 1
|
||||
//#include ThemedView.java.frag
|
||||
|
@ -1,4 +1,5 @@
|
||||
//#filter substitution
|
||||
//#define VIEW_NAME_SUFFIX TextView
|
||||
//#define BASE_TYPE android.widget.TextView
|
||||
//#define STYLE_CONSTRUCTOR 1
|
||||
//#include ThemedView.java.frag
|
||||
|
@ -15,7 +15,7 @@ import android.util.AttributeSet;
|
||||
|
||||
public class Themed@VIEW_NAME_SUFFIX@ extends @BASE_TYPE@
|
||||
implements LightweightTheme.OnChangeListener {
|
||||
private final LightweightTheme mTheme;
|
||||
private LightweightTheme mTheme;
|
||||
|
||||
private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
|
||||
private static final int[] STATE_LIGHT = { R.attr.state_light };
|
||||
@ -32,9 +32,20 @@ public class Themed@VIEW_NAME_SUFFIX@ extends @BASE_TYPE@
|
||||
|
||||
public Themed@VIEW_NAME_SUFFIX@(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize(context, attrs);
|
||||
}
|
||||
|
||||
//#ifdef STYLE_CONSTRUCTOR
|
||||
public Themed@VIEW_NAME_SUFFIX@(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initialize(context, attrs);
|
||||
}
|
||||
|
||||
//#endif
|
||||
private void initialize(final Context context, final AttributeSet attrs) {
|
||||
mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme();
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
|
||||
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
|
||||
mAutoUpdateTheme = a.getBoolean(R.styleable.LightweightTheme_autoUpdateTheme, true);
|
||||
a.recycle();
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
//#filter substitution
|
||||
//#define VIEW_NAME_SUFFIX View
|
||||
//#define BASE_TYPE android.view.View
|
||||
//#define STYLE_CONSTRUCTOR 1
|
||||
//#include ThemedView.java.frag
|
||||
|
@ -142,6 +142,10 @@ public final class Reporter extends BroadcastReceiver {
|
||||
|
||||
Map<String, ScanResult> currentWifiData = mBundle.getWifiData();
|
||||
for (ScanResult result : results) {
|
||||
if (currentWifiData.size() > MAX_WIFIS_PER_LOCATION) {
|
||||
return;
|
||||
}
|
||||
|
||||
String key = result.BSSID;
|
||||
if (!currentWifiData.containsKey(key)) {
|
||||
currentWifiData.put(key, result);
|
||||
@ -156,6 +160,9 @@ public final class Reporter extends BroadcastReceiver {
|
||||
|
||||
Map<String, CellInfo> currentCellData = mBundle.getCellData();
|
||||
for (CellInfo result : cells) {
|
||||
if (currentCellData.size() > MAX_CELLS_PER_LOCATION) {
|
||||
return;
|
||||
}
|
||||
String key = result.getCellIdentity();
|
||||
if (!currentCellData.containsKey(key)) {
|
||||
currentCellData.put(key, result);
|
||||
@ -191,7 +198,9 @@ public final class Reporter extends BroadcastReceiver {
|
||||
Log.d(LOG_TAG, "Received bundle: " + mlsObj.toString());
|
||||
}
|
||||
|
||||
AppGlobals.guiLogInfo(mlsObj.toString());
|
||||
if (wifiCount + cellCount < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
DataStorageManager.getInstance().insert(mlsObj.toString(), wifiCount, cellCount);
|
||||
|
@ -47,7 +47,8 @@ this.SearchSuggestionController = function SearchSuggestionController(callback =
|
||||
|
||||
this.SearchSuggestionController.prototype = {
|
||||
/**
|
||||
* The maximum number of local form history results to return.
|
||||
* The maximum number of local form history results to return. This limit is
|
||||
* only enforced if remote results are also returned.
|
||||
*/
|
||||
maxLocalResults: 7,
|
||||
|
||||
@ -200,8 +201,7 @@ this.SearchSuggestionController.prototype = {
|
||||
return;
|
||||
}
|
||||
let fhEntries = [];
|
||||
let maxHistoryItems = Math.min(result.matchCount, this.maxLocalResults);
|
||||
for (let i = 0; i < maxHistoryItems; ++i) {
|
||||
for (let i = 0; i < result.matchCount; ++i) {
|
||||
fhEntries.push(result.getValueAt(i));
|
||||
}
|
||||
deferredFormHistory.resolve({
|
||||
@ -335,8 +335,13 @@ this.SearchSuggestionController.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
// If we have remote results, cap the number of local results
|
||||
if (results.remote.length) {
|
||||
results.local = results.local.slice(0, this.maxLocalResults);
|
||||
}
|
||||
|
||||
// We don't want things to appear in both history and suggestions so remove entries from
|
||||
// remote results that are alrady in local.
|
||||
// remote results that are already in local.
|
||||
if (results.remote.length && results.local.length) {
|
||||
for (let i = 0; i < results.local.length; ++i) {
|
||||
let term = results.local[i];
|
||||
|
@ -25,6 +25,7 @@ SuggestAutoComplete.prototype = {
|
||||
|
||||
_init: function() {
|
||||
this._suggestionController = new SearchSuggestionController(obj => this.onResultsReturned(obj));
|
||||
this._suggestionController.maxLocalResults = this._historyLimit;
|
||||
},
|
||||
|
||||
get _suggestionLabel() {
|
||||
@ -57,8 +58,7 @@ SuggestAutoComplete.prototype = {
|
||||
let finalComments = [];
|
||||
|
||||
// If form history has results, add them to the list.
|
||||
let maxHistoryItems = Math.min(results.local.length, this._historyLimit);
|
||||
for (let i = 0; i < maxHistoryItems; ++i) {
|
||||
for (let i = 0; i < results.local.length; ++i) {
|
||||
finalResults.push(results.local[i]);
|
||||
finalComments.push("");
|
||||
}
|
||||
|
@ -149,7 +149,6 @@ add_task(function* simple_non_ascii() {
|
||||
do_check_eq(result.remote[0], "I ❤️ Mozilla");
|
||||
});
|
||||
|
||||
|
||||
add_task(function* both_local_remote_result_dedupe() {
|
||||
yield updateSearchHistory("bump", "Mozilla");
|
||||
|
||||
@ -269,6 +268,36 @@ add_task(function* both_identical_with_more_than_max_results() {
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* noremote_maxLocal() {
|
||||
let controller = new SearchSuggestionController();
|
||||
controller.maxLocalResults = 2; // (should be ignored because no remote results)
|
||||
controller.maxRemoteResults = 0;
|
||||
let result = yield controller.fetch("letter ", false, getEngine);
|
||||
do_check_eq(result.term, "letter ");
|
||||
do_check_eq(result.local.length, 26);
|
||||
for (let i = 0; i < result.local.length; i++) {
|
||||
do_check_eq(result.local[i], "letter " + String.fromCharCode("A".charCodeAt() + i));
|
||||
}
|
||||
do_check_eq(result.remote.length, 0);
|
||||
});
|
||||
|
||||
add_task(function* someremote_maxLocal() {
|
||||
let controller = new SearchSuggestionController();
|
||||
controller.maxLocalResults = 2;
|
||||
controller.maxRemoteResults = 2;
|
||||
let result = yield controller.fetch("letter ", false, getEngine);
|
||||
do_check_eq(result.term, "letter ");
|
||||
do_check_eq(result.local.length, 2);
|
||||
for (let i = 0; i < result.local.length; i++) {
|
||||
do_check_eq(result.local[i], "letter " + String.fromCharCode("A".charCodeAt() + i));
|
||||
}
|
||||
do_check_eq(result.remote.length, 2);
|
||||
// "A" and "B" will have been de-duped, start at C for remote results
|
||||
for (let i = 0; i < result.remote.length; i++) {
|
||||
do_check_eq(result.remote[i], "letter " + String.fromCharCode("C".charCodeAt() + i));
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* one_of_each() {
|
||||
let controller = new SearchSuggestionController();
|
||||
controller.maxLocalResults = 1;
|
||||
@ -288,8 +317,10 @@ add_task(function* local_result_returned_remote_result_disabled() {
|
||||
controller.maxRemoteResults = 1;
|
||||
let result = yield controller.fetch("letter ", false, getEngine);
|
||||
do_check_eq(result.term, "letter ");
|
||||
do_check_eq(result.local.length, 1);
|
||||
do_check_eq(result.local[0], "letter A");
|
||||
do_check_eq(result.local.length, 26);
|
||||
for (let i = 0; i < 26; i++) {
|
||||
do_check_eq(result.local[i], "letter " + String.fromCharCode("A".charCodeAt() + i));
|
||||
}
|
||||
do_check_eq(result.remote.length, 0);
|
||||
Services.prefs.setBoolPref("browser.search.suggest.enabled", true);
|
||||
});
|
||||
@ -301,8 +332,10 @@ add_task(function* local_result_returned_remote_result_disabled_after_creation_o
|
||||
Services.prefs.setBoolPref("browser.search.suggest.enabled", false);
|
||||
let result = yield controller.fetch("letter ", false, getEngine);
|
||||
do_check_eq(result.term, "letter ");
|
||||
do_check_eq(result.local.length, 1);
|
||||
do_check_eq(result.local[0], "letter A");
|
||||
do_check_eq(result.local.length, 26);
|
||||
for (let i = 0; i < 26; i++) {
|
||||
do_check_eq(result.local[i], "letter " + String.fromCharCode("A".charCodeAt() + i));
|
||||
}
|
||||
do_check_eq(result.remote.length, 0);
|
||||
Services.prefs.setBoolPref("browser.search.suggest.enabled", true);
|
||||
});
|
||||
@ -331,8 +364,10 @@ add_task(function* one_local_zero_remote() {
|
||||
controller.maxRemoteResults = 0;
|
||||
let result = yield controller.fetch("letter ", false, getEngine);
|
||||
do_check_eq(result.term, "letter ");
|
||||
do_check_eq(result.local.length, 1);
|
||||
do_check_eq(result.local[0], "letter A");
|
||||
do_check_eq(result.local.length, 26);
|
||||
for (let i = 0; i < 26; i++) {
|
||||
do_check_eq(result.local[i], "letter " + String.fromCharCode("A".charCodeAt() + i));
|
||||
}
|
||||
do_check_eq(result.remote.length, 0);
|
||||
});
|
||||
|
||||
|
105
toolkit/components/thumbnails/PageThumbUtils.jsm
Normal file
105
toolkit/components/thumbnails/PageThumbUtils.jsm
Normal file
@ -0,0 +1,105 @@
|
||||
/* 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/. */
|
||||
|
||||
/*
|
||||
* Common thumbnailing routines used by various consumers, including
|
||||
* PageThumbs and backgroundPageThumbsContent.
|
||||
*/
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["PageThumbUtils"];
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm", this);
|
||||
|
||||
this.PageThumbUtils = {
|
||||
// The default background color for page thumbnails.
|
||||
THUMBNAIL_BG_COLOR: "#fff",
|
||||
// The namespace for thumbnail canvas elements.
|
||||
HTML_NAMESPACE: "http://www.w3.org/1999/xhtml",
|
||||
|
||||
/**
|
||||
* Creates a new canvas element in the context of aWindow, or if aWindow
|
||||
* is undefined, in the context of hiddenDOMWindow.
|
||||
*
|
||||
* @param aWindow (optional) The document of this window will be used to
|
||||
* create the canvas. If not given, the hidden window will be used.
|
||||
* @return The newly created canvas.
|
||||
*/
|
||||
createCanvas: function (aWindow) {
|
||||
let doc = (aWindow || Services.appShell.hiddenDOMWindow).document;
|
||||
let canvas = doc.createElementNS(this.HTML_NAMESPACE, "canvas");
|
||||
canvas.mozOpaque = true;
|
||||
canvas.mozImageSmoothingEnabled = true;
|
||||
let [thumbnailWidth, thumbnailHeight] = this.getThumbnailSize();
|
||||
canvas.width = thumbnailWidth;
|
||||
canvas.height = thumbnailHeight;
|
||||
return canvas;
|
||||
},
|
||||
|
||||
/**
|
||||
* Calculates a preferred initial thumbnail size based on current desktop
|
||||
* dimensions. The resulting dims will generally be about 1/3 the
|
||||
* size of the desktop. (jimm: why??)
|
||||
*
|
||||
* @return The calculated thumbnail size or a default if unable to calculate.
|
||||
*/
|
||||
getThumbnailSize: function () {
|
||||
if (!this._thumbnailWidth || !this._thumbnailHeight) {
|
||||
let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"]
|
||||
.getService(Ci.nsIScreenManager);
|
||||
let left = {}, top = {}, width = {}, height = {};
|
||||
screenManager.primaryScreen.GetRectDisplayPix(left, top, width, height);
|
||||
this._thumbnailWidth = Math.round(width.value / 3);
|
||||
this._thumbnailHeight = Math.round(height.value / 3);
|
||||
}
|
||||
return [this._thumbnailWidth, this._thumbnailHeight];
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine a good thumbnail crop size and scale for a given content
|
||||
* window.
|
||||
*
|
||||
* @param aWindow The content window.
|
||||
* @param aCanvas The target canvas.
|
||||
* @return An array containing width, height and scale.
|
||||
*/
|
||||
determineCropSize: function (aWindow, aCanvas) {
|
||||
if (Cu.isCrossProcessWrapper(aWindow)) {
|
||||
throw new Error('Do not pass cpows here.');
|
||||
}
|
||||
let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
// aWindow may be a cpow, add exposed props security values.
|
||||
let sbWidth = {}, sbHeight = {};
|
||||
|
||||
try {
|
||||
utils.getScrollbarSize(false, sbWidth, sbHeight);
|
||||
} catch (e) {
|
||||
// This might fail if the window does not have a presShell.
|
||||
Cu.reportError("Unable to get scrollbar size in determineCropSize.");
|
||||
sbWidth.value = sbHeight.value = 0;
|
||||
}
|
||||
|
||||
// Even in RTL mode, scrollbars are always on the right.
|
||||
// So there's no need to determine a left offset.
|
||||
let width = aWindow.innerWidth - sbWidth.value;
|
||||
let height = aWindow.innerHeight - sbHeight.value;
|
||||
|
||||
let {width: thumbnailWidth, height: thumbnailHeight} = aCanvas;
|
||||
let scale = Math.min(Math.max(thumbnailWidth / width, thumbnailHeight / height), 1);
|
||||
let scaledWidth = width * scale;
|
||||
let scaledHeight = height * scale;
|
||||
|
||||
if (scaledHeight > thumbnailHeight)
|
||||
height -= Math.floor(Math.abs(scaledHeight - thumbnailHeight) * scale);
|
||||
|
||||
if (scaledWidth > thumbnailWidth)
|
||||
width -= Math.floor(Math.abs(scaledWidth - thumbnailWidth) * scale);
|
||||
|
||||
return [width, height, scale];
|
||||
}
|
||||
};
|
@ -10,13 +10,14 @@ const Cu = Components.utils;
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
|
||||
const PREF_STORAGE_VERSION = "browser.pagethumbnails.storage_version";
|
||||
const LATEST_STORAGE_VERSION = 3;
|
||||
|
||||
const EXPIRATION_MIN_CHUNK_SIZE = 50;
|
||||
const EXPIRATION_INTERVAL_SECS = 3600;
|
||||
|
||||
var gRemoteThumbId = 0;
|
||||
|
||||
// If a request for a thumbnail comes in and we find one that is "stale"
|
||||
// (or don't find one at all) we automatically queue a request to generate a
|
||||
// new one.
|
||||
@ -27,11 +28,6 @@ const MAX_THUMBNAIL_AGE_SECS = 172800; // 2 days == 60*60*24*2 == 172800 secs.
|
||||
*/
|
||||
const THUMBNAIL_DIRECTORY = "thumbnails";
|
||||
|
||||
/**
|
||||
* The default background color for page thumbnails.
|
||||
*/
|
||||
const THUMBNAIL_BG_COLOR = "#fff";
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
|
||||
Cu.import("resource://gre/modules/PromiseWorker.jsm", this);
|
||||
Cu.import("resource://gre/modules/Promise.jsm", this);
|
||||
@ -69,6 +65,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
|
||||
"resource://gre/modules/Deprecated.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
|
||||
"resource://gre/modules/AsyncShutdown.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PageThumbUtils",
|
||||
"resource://gre/modules/PageThumbUtils.jsm");
|
||||
|
||||
/**
|
||||
* Utilities for dealing with promises and Task.jsm
|
||||
@ -168,72 +166,73 @@ this.PageThumbs = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Captures a thumbnail for the given window.
|
||||
* @param aWindow The DOM window to capture a thumbnail from.
|
||||
* @param aCallback The function to be called when the thumbnail has been
|
||||
* captured. The first argument will be the data stream
|
||||
* containing the image data.
|
||||
*/
|
||||
capture: function PageThumbs_capture(aWindow, aCallback) {
|
||||
if (!this._prefEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let canvas = this.createCanvas();
|
||||
this.captureToCanvas(aWindow, canvas);
|
||||
|
||||
// Fetch the canvas data on the next event loop tick so that we allow
|
||||
// some event processing in between drawing to the canvas and encoding
|
||||
// its data. We want to block the UI as short as possible. See bug 744100.
|
||||
Services.tm.currentThread.dispatch(function () {
|
||||
canvas.mozFetchAsStream(aCallback, this.contentType);
|
||||
}.bind(this), Ci.nsIThread.DISPATCH_NORMAL);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Captures a thumbnail for the given window.
|
||||
* Asynchronously returns a thumbnail as a blob for the given
|
||||
* window.
|
||||
*
|
||||
* @param aWindow The DOM window to capture a thumbnail from.
|
||||
* @param aBrowser The <browser> to capture a thumbnail from.
|
||||
* @return {Promise}
|
||||
* @resolve {Blob} The thumbnail, as a Blob.
|
||||
*/
|
||||
captureToBlob: function PageThumbs_captureToBlob(aWindow) {
|
||||
captureToBlob: function PageThumbs_captureToBlob(aBrowser) {
|
||||
if (!this._prefEnabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let canvas = this.createCanvas();
|
||||
this.captureToCanvas(aWindow, canvas);
|
||||
|
||||
let deferred = Promise.defer();
|
||||
let type = this.contentType;
|
||||
// Fetch the canvas data on the next event loop tick so that we allow
|
||||
// some event processing in between drawing to the canvas and encoding
|
||||
// its data. We want to block the UI as short as possible. See bug 744100.
|
||||
canvas.toBlob(function asBlob(blob) {
|
||||
deferred.resolve(blob, type);
|
||||
|
||||
let canvas = this.createCanvas();
|
||||
this.captureToCanvas(aBrowser, canvas, () => {
|
||||
canvas.toBlob(blob => {
|
||||
deferred.resolve(blob, this.contentType);
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Captures a thumbnail from a given window and draws it to the given canvas.
|
||||
* @param aWindow The DOM window to capture a thumbnail from.
|
||||
* Note, when dealing with remote content, this api draws into the passed
|
||||
* canvas asynchronously. Pass aCallback to receive an async callback after
|
||||
* canvas painting has completed.
|
||||
* @param aBrowser The browser to capture a thumbnail from.
|
||||
* @param aCanvas The canvas to draw to.
|
||||
* @param aCallback (optional) A callback invoked once the thumbnail has been
|
||||
* rendered to aCanvas.
|
||||
*/
|
||||
captureToCanvas: function PageThumbs_captureToCanvas(aWindow, aCanvas) {
|
||||
captureToCanvas: function PageThumbs_captureToCanvas(aBrowser, aCanvas, aCallback) {
|
||||
let telemetryCaptureTime = new Date();
|
||||
this._captureToCanvas(aWindow, aCanvas);
|
||||
let telemetry = Services.telemetry;
|
||||
telemetry.getHistogramById("FX_THUMBNAILS_CAPTURE_TIME_MS")
|
||||
.add(new Date() - telemetryCaptureTime);
|
||||
this._captureToCanvas(aBrowser, aCanvas, function () {
|
||||
Services.telemetry
|
||||
.getHistogramById("FX_THUMBNAILS_CAPTURE_TIME_MS")
|
||||
.add(new Date() - telemetryCaptureTime);
|
||||
if (aCallback) {
|
||||
aCallback(aCanvas);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// The background thumbnail service captures to canvas but doesn't want to
|
||||
// participate in this service's telemetry, which is why this method exists.
|
||||
_captureToCanvas: function PageThumbs__captureToCanvas(aWindow, aCanvas) {
|
||||
let [sw, sh, scale] = this._determineCropSize(aWindow, aCanvas);
|
||||
_captureToCanvas: function (aBrowser, aCanvas, aCallback) {
|
||||
if (aBrowser.isRemoteBrowser) {
|
||||
Task.spawn(function () {
|
||||
let data =
|
||||
yield this._captureRemoteThumbnail(aBrowser, aCanvas);
|
||||
let canvas = data.thumbnail;
|
||||
let ctx = canvas.getContext("2d");
|
||||
let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
aCanvas.getContext("2d").putImageData(imgData, 0, 0);
|
||||
if (aCallback) {
|
||||
aCallback(aCanvas);
|
||||
}
|
||||
}.bind(this));
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate in-process content thumbnail
|
||||
let [width, height, scale] =
|
||||
PageThumbUtils.determineCropSize(aBrowser.contentWindow, aCanvas);
|
||||
let ctx = aCanvas.getContext("2d");
|
||||
|
||||
// Scale the canvas accordingly.
|
||||
@ -242,15 +241,78 @@ this.PageThumbs = {
|
||||
|
||||
try {
|
||||
// Draw the window contents to the canvas.
|
||||
ctx.drawWindow(aWindow, 0, 0, sw, sh, THUMBNAIL_BG_COLOR,
|
||||
ctx.drawWindow(aBrowser.contentWindow, 0, 0, width, height,
|
||||
PageThumbUtils.THUMBNAIL_BG_COLOR,
|
||||
ctx.DRAWWINDOW_DO_NOT_FLUSH);
|
||||
} catch (e) {
|
||||
// We couldn't draw to the canvas for some reason.
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
|
||||
if (aCallback) {
|
||||
aCallback(aCanvas);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Asynchrnously render an appropriately scaled thumbnail to canvas.
|
||||
*
|
||||
* @param aBrowser The browser to capture a thumbnail from.
|
||||
* @param aCanvas The canvas to draw to.
|
||||
* @return a promise
|
||||
*/
|
||||
_captureRemoteThumbnail: function (aBrowser, aCanvas) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
// The index we send with the request so we can identify the
|
||||
// correct response.
|
||||
let index = gRemoteThumbId++;
|
||||
|
||||
// Thumbnail request response handler
|
||||
let mm = aBrowser.messageManager;
|
||||
|
||||
// Browser:Thumbnail:Response handler
|
||||
let thumbFunc = function (aMsg) {
|
||||
// Ignore events unrelated to our request
|
||||
if (aMsg.data.id != index) {
|
||||
return;
|
||||
}
|
||||
|
||||
mm.removeMessageListener("Browser:Thumbnail:Response", thumbFunc);
|
||||
let imageBlob = aMsg.data.thumbnail;
|
||||
let doc = aBrowser.parentElement.ownerDocument;
|
||||
let reader = Cc["@mozilla.org/files/filereader;1"].
|
||||
createInstance(Ci.nsIDOMFileReader);
|
||||
reader.addEventListener("loadend", function() {
|
||||
let image = doc.createElementNS(PageThumbUtils.HTML_NAMESPACE, "img");
|
||||
image.onload = function () {
|
||||
let thumbnail = doc.createElementNS(PageThumbUtils.HTML_NAMESPACE, "canvas");
|
||||
thumbnail.width = image.naturalWidth;
|
||||
thumbnail.height = image.naturalHeight;
|
||||
let ctx = thumbnail.getContext("2d");
|
||||
ctx.drawImage(image, 0, 0);
|
||||
deferred.resolve({
|
||||
thumbnail: thumbnail
|
||||
});
|
||||
}
|
||||
image.src = reader.result;
|
||||
});
|
||||
// xxx wish there was a way to skip this encoding step
|
||||
reader.readAsDataURL(imageBlob);
|
||||
}
|
||||
|
||||
// Send a thumbnail request
|
||||
mm.addMessageListener("Browser:Thumbnail:Response", thumbFunc);
|
||||
mm.sendAsyncMessage("Browser:Thumbnail:Request", {
|
||||
canvasWidth: aCanvas.width,
|
||||
canvasHeight: aCanvas.height,
|
||||
background: PageThumbUtils.THUMBNAIL_BG_COLOR,
|
||||
id: index
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Captures a thumbnail for the given browser and stores it to the cache.
|
||||
* @param aBrowser The browser to capture a thumbnail for.
|
||||
@ -262,19 +324,27 @@ this.PageThumbs = {
|
||||
}
|
||||
|
||||
let url = aBrowser.currentURI.spec;
|
||||
let channel = aBrowser.docShell.currentDocumentChannel;
|
||||
let originalURL = channel.originalURI.spec;
|
||||
let originalURL;
|
||||
let channelError = false;
|
||||
|
||||
// see if this was an error response.
|
||||
let wasError = this._isChannelErrorResponse(channel);
|
||||
if (!aBrowser.isRemoteBrowser) {
|
||||
let channel = aBrowser.docShell.currentDocumentChannel;
|
||||
originalURL = channel.originalURI.spec;
|
||||
// see if this was an error response.
|
||||
channelError = this._isChannelErrorResponse(channel);
|
||||
} else {
|
||||
// We need channel info (bug 1073957)
|
||||
originalURL = url;
|
||||
}
|
||||
|
||||
Task.spawn((function task() {
|
||||
let isSuccess = true;
|
||||
try {
|
||||
let blob = yield this.captureToBlob(aBrowser.contentWindow);
|
||||
let blob = yield this.captureToBlob(aBrowser);
|
||||
let buffer = yield TaskUtils.readBlob(blob);
|
||||
yield this._store(originalURL, url, buffer, wasError);
|
||||
} catch (_) {
|
||||
yield this._store(originalURL, url, buffer, channelError);
|
||||
} catch (ex) {
|
||||
Components.utils.reportError("Exception thrown during thumbnail capture: '" + ex + "'");
|
||||
isSuccess = false;
|
||||
}
|
||||
if (aCallback) {
|
||||
@ -374,44 +444,6 @@ this.PageThumbs = {
|
||||
PageThumbsExpiration.removeFilter(aFilter);
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines the crop size for a given content window.
|
||||
* @param aWindow The content window.
|
||||
* @param aCanvas The target canvas.
|
||||
* @return An array containing width, height and scale.
|
||||
*/
|
||||
_determineCropSize: function PageThumbs_determineCropSize(aWindow, aCanvas) {
|
||||
let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
let sbWidth = {}, sbHeight = {};
|
||||
|
||||
try {
|
||||
utils.getScrollbarSize(false, sbWidth, sbHeight);
|
||||
} catch (e) {
|
||||
// This might fail if the window does not have a presShell.
|
||||
Cu.reportError("Unable to get scrollbar size in _determineCropSize.");
|
||||
sbWidth.value = sbHeight.value = 0;
|
||||
}
|
||||
|
||||
// Even in RTL mode, scrollbars are always on the right.
|
||||
// So there's no need to determine a left offset.
|
||||
let sw = aWindow.innerWidth - sbWidth.value;
|
||||
let sh = aWindow.innerHeight - sbHeight.value;
|
||||
|
||||
let {width: thumbnailWidth, height: thumbnailHeight} = aCanvas;
|
||||
let scale = Math.min(Math.max(thumbnailWidth / sw, thumbnailHeight / sh), 1);
|
||||
let scaledWidth = sw * scale;
|
||||
let scaledHeight = sh * scale;
|
||||
|
||||
if (scaledHeight > thumbnailHeight)
|
||||
sh -= Math.floor(Math.abs(scaledHeight - thumbnailHeight) * scale);
|
||||
|
||||
if (scaledWidth > thumbnailWidth)
|
||||
sw -= Math.floor(Math.abs(scaledWidth - thumbnailWidth) * scale);
|
||||
|
||||
return [sw, sh, scale];
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new hidden canvas element.
|
||||
* @param aWindow The document of this window will be used to create the
|
||||
@ -419,30 +451,7 @@ this.PageThumbs = {
|
||||
* @return The newly created canvas.
|
||||
*/
|
||||
createCanvas: function PageThumbs_createCanvas(aWindow) {
|
||||
let doc = (aWindow || Services.appShell.hiddenDOMWindow).document;
|
||||
let canvas = doc.createElementNS(HTML_NAMESPACE, "canvas");
|
||||
canvas.mozOpaque = true;
|
||||
canvas.mozImageSmoothingEnabled = true;
|
||||
let [thumbnailWidth, thumbnailHeight] = this._getThumbnailSize();
|
||||
canvas.width = thumbnailWidth;
|
||||
canvas.height = thumbnailHeight;
|
||||
return canvas;
|
||||
},
|
||||
|
||||
/**
|
||||
* Calculates the thumbnail size based on current desktop's dimensions.
|
||||
* @return The calculated thumbnail size or a default if unable to calculate.
|
||||
*/
|
||||
_getThumbnailSize: function PageThumbs_getThumbnailSize() {
|
||||
if (!this._thumbnailWidth || !this._thumbnailHeight) {
|
||||
let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"]
|
||||
.getService(Ci.nsIScreenManager);
|
||||
let left = {}, top = {}, width = {}, height = {};
|
||||
screenManager.primaryScreen.GetRectDisplayPix(left, top, width, height);
|
||||
this._thumbnailWidth = Math.round(width.value / 3);
|
||||
this._thumbnailHeight = Math.round(height.value / 3);
|
||||
}
|
||||
return [this._thumbnailWidth, this._thumbnailHeight];
|
||||
return PageThumbUtils.createCanvas(aWindow);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -2,13 +2,11 @@
|
||||
* 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/. */
|
||||
|
||||
(function () { // bug 673569 workaround :(
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.importGlobalProperties(['Blob']);
|
||||
|
||||
Cu.import("resource://gre/modules/PageThumbs.jsm");
|
||||
Cu.import("resource://gre/modules/PageThumbUtils.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
@ -49,7 +47,7 @@ const backgroundPageThumbsContent = {
|
||||
// in the parent (eg, auth) aren't prevented, but alert() etc are.
|
||||
// disableDialogs only works on the current inner window, so it has
|
||||
// to be called every page load, but before scripts run.
|
||||
if (subj == content.document) {
|
||||
if (content && subj == content.document) {
|
||||
content.
|
||||
QueryInterface(Ci.nsIInterfaceRequestor).
|
||||
getInterface(Ci.nsIDOMWindowUtils).
|
||||
@ -129,9 +127,19 @@ const backgroundPageThumbsContent = {
|
||||
capture.finalURL = this._webNav.currentURI.spec;
|
||||
capture.pageLoadTime = new Date() - capture.pageLoadStartDate;
|
||||
|
||||
let canvas = PageThumbs.createCanvas(content);
|
||||
let canvasDrawDate = new Date();
|
||||
PageThumbs._captureToCanvas(content, canvas);
|
||||
|
||||
let canvas = PageThumbUtils.createCanvas(content);
|
||||
let [sw, sh, scale] = PageThumbUtils.determineCropSize(content, canvas);
|
||||
|
||||
let ctx = canvas.getContext("2d");
|
||||
ctx.save();
|
||||
ctx.scale(scale, scale);
|
||||
ctx.drawWindow(content, 0, 0, sw, sh,
|
||||
PageThumbUtils.THUMBNAIL_BG_COLOR,
|
||||
ctx.DRAWWINDOW_DO_NOT_FLUSH);
|
||||
ctx.restore();
|
||||
|
||||
capture.canvasDrawTime = new Date() - canvasDrawDate;
|
||||
|
||||
canvas.toBlob(blob => {
|
||||
@ -184,5 +192,3 @@ const backgroundPageThumbsContent = {
|
||||
};
|
||||
|
||||
backgroundPageThumbsContent.init();
|
||||
|
||||
})();
|
||||
|
@ -15,6 +15,7 @@ EXTRA_JS_MODULES += [
|
||||
'BackgroundPageThumbs.jsm',
|
||||
'PageThumbs.jsm',
|
||||
'PageThumbsWorker.js',
|
||||
'PageThumbUtils.jsm',
|
||||
]
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
@ -1,5 +1,4 @@
|
||||
[DEFAULT]
|
||||
skip-if = e10s # Bug 863512 - thumbnails are disabled with e10s enabled.
|
||||
support-files =
|
||||
background_red.html
|
||||
background_red_redirect.sjs
|
||||
@ -12,15 +11,16 @@ support-files =
|
||||
|
||||
[browser_thumbnails_bg_bad_url.js]
|
||||
[browser_thumbnails_bg_crash_during_capture.js]
|
||||
skip-if = buildapp == 'mulet' || !crashreporter
|
||||
skip-if = buildapp == 'mulet' || !crashreporter || e10s # crashing the remote thumbnailer crashes the remote test tab
|
||||
[browser_thumbnails_bg_crash_while_idle.js]
|
||||
skip-if = buildapp == 'mulet' || !crashreporter
|
||||
skip-if = buildapp == 'mulet' || !crashreporter || e10s
|
||||
[browser_thumbnails_bg_basic.js]
|
||||
[browser_thumbnails_bg_queueing.js]
|
||||
[browser_thumbnails_bg_timeout.js]
|
||||
[browser_thumbnails_bg_redirect.js]
|
||||
[browser_thumbnails_bg_destroy_browser.js]
|
||||
[browser_thumbnails_bg_no_cookies_sent.js]
|
||||
skip-if = e10s # e10s cookie problems
|
||||
[browser_thumbnails_bg_no_cookies_stored.js]
|
||||
[browser_thumbnails_bg_no_auth_prompt.js]
|
||||
[browser_thumbnails_bg_no_alert.js]
|
||||
@ -29,12 +29,18 @@ skip-if = buildapp == 'mulet' || !crashreporter
|
||||
[browser_thumbnails_bug726727.js]
|
||||
skip-if = buildapp == 'mulet'
|
||||
[browser_thumbnails_bug727765.js]
|
||||
skip-if = e10s # tries to open crypto/local file from the child
|
||||
[browser_thumbnails_bug818225.js]
|
||||
skip-if = e10s # load event issues, bug 1084637.
|
||||
[browser_thumbnails_capture.js]
|
||||
skip-if = e10s # tries to call drawWindow with a remote browser.
|
||||
[browser_thumbnails_expiration.js]
|
||||
[browser_thumbnails_privacy.js]
|
||||
skip-if = e10s # nsSSLStatus has null mServerCert, bug 820466
|
||||
[browser_thumbnails_redirect.js]
|
||||
skip-if = e10s # bug 1050869
|
||||
[browser_thumbnails_storage.js]
|
||||
[browser_thumbnails_storage_migrate3.js]
|
||||
skip-if = buildapp == 'mulet'
|
||||
[browser_thumbnails_update.js]
|
||||
skip-if = e10s # tries to open crypto/local file from the child
|
||||
|
@ -137,6 +137,7 @@ function captureAndCheckColor(aRed, aGreen, aBlue, aMessage) {
|
||||
/**
|
||||
* For a given URL, loads the corresponding thumbnail
|
||||
* to a canvas and passes its image data to the callback.
|
||||
* Note, not compat with e10s!
|
||||
* @param aURL The url associated with the thumbnail.
|
||||
* @param aCallback The function to pass the image data to.
|
||||
*/
|
||||
|
@ -11,6 +11,9 @@ Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
Cu.import("resource://gre/modules/RemoteAddonsChild.jsm");
|
||||
Cu.import("resource://gre/modules/Timer.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PageThumbUtils",
|
||||
"resource://gre/modules/PageThumbUtils.jsm");
|
||||
|
||||
#ifdef MOZ_CRASHREPORTER
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
|
||||
"@mozilla.org/xre/app-info;1",
|
||||
@ -368,6 +371,37 @@ addMessageListener("UpdateCharacterSet", function (aMessage) {
|
||||
docShell.gatherCharsetMenuTelemetry();
|
||||
});
|
||||
|
||||
/**
|
||||
* Remote thumbnail request handler for PageThumbs thumbnails.
|
||||
*/
|
||||
addMessageListener("Browser:Thumbnail:Request", function (aMessage) {
|
||||
let thumbnail = content.document.createElementNS(PageThumbUtils.HTML_NAMESPACE,
|
||||
"canvas");
|
||||
thumbnail.mozOpaque = true;
|
||||
thumbnail.mozImageSmoothingEnabled = true;
|
||||
|
||||
thumbnail.width = aMessage.data.canvasWidth;
|
||||
thumbnail.height = aMessage.data.canvasHeight;
|
||||
|
||||
let [width, height, scale] =
|
||||
PageThumbUtils.determineCropSize(content, thumbnail);
|
||||
|
||||
let ctx = thumbnail.getContext("2d");
|
||||
ctx.save();
|
||||
ctx.scale(scale, scale);
|
||||
ctx.drawWindow(content, 0, 0, width, height,
|
||||
aMessage.data.background,
|
||||
ctx.DRAWWINDOW_DO_NOT_FLUSH);
|
||||
ctx.restore();
|
||||
|
||||
thumbnail.toBlob(function (aBlob) {
|
||||
sendAsyncMessage("Browser:Thumbnail:Response", {
|
||||
thumbnail: aBlob,
|
||||
id: aMessage.data.id
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// The AddonsChild needs to be rooted so that it stays alive as long as
|
||||
// the tab.
|
||||
let AddonsChild;
|
||||
|
@ -51,7 +51,8 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
|
||||
*/
|
||||
"markers" : {
|
||||
type: "markers",
|
||||
markers: Arg(0, "array:json")
|
||||
markers: Arg(0, "array:json"),
|
||||
endTime: Arg(1, "number")
|
||||
},
|
||||
|
||||
/**
|
||||
@ -80,6 +81,7 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
|
||||
this.tabActor = tabActor;
|
||||
|
||||
this._isRecording = false;
|
||||
this._startTime = 0;
|
||||
|
||||
// Make sure to get markers from new windows as they become available
|
||||
this._onWindowReady = this._onWindowReady.bind(this);
|
||||
@ -142,7 +144,8 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
|
||||
markers = [...markers, ...docShell.popProfileTimelineMarkers()];
|
||||
}
|
||||
if (markers.length > 0) {
|
||||
events.emit(this, "markers", markers);
|
||||
let endTime = this.docShells[0].now();
|
||||
events.emit(this, "markers", markers, endTime);
|
||||
}
|
||||
if (this._memoryActor) {
|
||||
events.emit(this, "memory", Date.now(), this._memoryActor.measure());
|
||||
@ -176,6 +179,7 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
|
||||
return;
|
||||
}
|
||||
this._isRecording = true;
|
||||
this._startTime = this.docShells[0].now();
|
||||
|
||||
for (let docShell of this.docShells) {
|
||||
docShell.recordProfileTimelineMarkers = true;
|
||||
@ -191,10 +195,14 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
|
||||
}
|
||||
|
||||
this._pullTimelineData();
|
||||
return this._startTime;
|
||||
}, {
|
||||
request: {
|
||||
withMemory: Option(0, "boolean"),
|
||||
withTicks: Option(0, "boolean")
|
||||
},
|
||||
response: {
|
||||
value: RetVal("number")
|
||||
}
|
||||
}),
|
||||
|
||||
@ -228,8 +236,6 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
|
||||
*/
|
||||
_onWindowReady: function({window}) {
|
||||
if (this._isRecording) {
|
||||
// XXX As long as bug 1070089 isn't fixed, each docShell has its own start
|
||||
// recording time, so markers aren't going to be properly ordered.
|
||||
let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell);
|
||||
|
@ -292,6 +292,36 @@ let AudioNodeActor = exports.AudioNodeActor = protocol.ActorClass({
|
||||
response: { params: RetVal("json") }
|
||||
}),
|
||||
|
||||
/**
|
||||
* Connects this audionode to an AudioParam via `node.connect(param)`.
|
||||
*/
|
||||
connectParam: method(function (destActor, paramName, output) {
|
||||
let srcNode = this.node.get();
|
||||
let destNode = destActor.node.get();
|
||||
|
||||
if (srcNode === null || destNode === null) {
|
||||
return CollectedAudioNodeError();
|
||||
}
|
||||
|
||||
try {
|
||||
// Connect via the unwrapped node, so we can call the
|
||||
// patched method that fires the webaudio actor's `connect-param` event.
|
||||
// Connect directly to the wrapped `destNode`, otherwise
|
||||
// the patched method thinks this is a new node and won't be
|
||||
// able to find it in `_nativeToActorID`.
|
||||
XPCNativeWrapper.unwrap(srcNode).connect(destNode[paramName], output);
|
||||
} catch (e) {
|
||||
return constructError(e);
|
||||
}
|
||||
}, {
|
||||
request: {
|
||||
destActor: Arg(0, "audionode"),
|
||||
paramName: Arg(1, "string"),
|
||||
output: Arg(2, "nullable:number")
|
||||
},
|
||||
response: { error: RetVal("nullable:json") }
|
||||
}),
|
||||
|
||||
/**
|
||||
* Connects this audionode to another via `node.connect(dest)`.
|
||||
*/
|
||||
|
@ -135,7 +135,9 @@ var SimpleServiceDiscovery = {
|
||||
|
||||
_usingLAN: function() {
|
||||
let network = Cc["@mozilla.org/network/network-link-service;1"].getService(Ci.nsINetworkLinkService);
|
||||
return (network.linkType == Ci.nsINetworkLinkService.LINK_TYPE_WIFI || network.linkType == Ci.nsINetworkLinkService.LINK_TYPE_ETHERNET);
|
||||
return (network.linkType == Ci.nsINetworkLinkService.LINK_TYPE_WIFI ||
|
||||
network.linkType == Ci.nsINetworkLinkService.LINK_TYPE_ETHERNET ||
|
||||
network.linkType == Ci.nsINetworkLinkService.LINK_TYPE_UNKNOWN);
|
||||
},
|
||||
|
||||
_search: function _search() {
|
||||
|
@ -34,6 +34,7 @@ menubar, toolbar[type="menubar"] {
|
||||
menubar:-moz-lwtheme,
|
||||
toolbar:-moz-lwtheme {
|
||||
-moz-appearance: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* in browser.xul, the menubar is inside a toolbar... */
|
||||
|
Loading…
Reference in New Issue
Block a user