Merge mozilla-central to mozilla-inbound

This commit is contained in:
Ed Morley 2012-01-20 01:27:04 +00:00
commit 28b141d8f2
58 changed files with 1243 additions and 50 deletions

View File

@ -1041,6 +1041,7 @@ pref("devtools.scratchpad.enabled", true);
// Enable the Style Editor.
pref("devtools.styleeditor.enabled", true);
pref("devtools.styleeditor.transitions", true);
// Enable tools for Chrome development.
pref("devtools.chrome.enabled", false);

View File

@ -0,0 +1,119 @@
#ifdef 0
/* 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/. */
#endif
/**
* Keeps thumbnails of open web pages up-to-date.
*/
let gBrowserThumbnails = {
_captureDelayMS: 2000,
/**
* Map of capture() timeouts assigned to their browsers.
*/
_timeouts: null,
/**
* Cache for the PageThumbs module.
*/
_pageThumbs: null,
/**
* List of tab events we want to listen for.
*/
_tabEvents: ["TabClose", "TabSelect"],
init: function Thumbnails_init() {
gBrowser.addTabsProgressListener(this);
this._tabEvents.forEach(function (aEvent) {
gBrowser.tabContainer.addEventListener(aEvent, this, false);
}, this);
this._timeouts = new WeakMap();
XPCOMUtils.defineLazyModuleGetter(this, "_pageThumbs",
"resource:///modules/PageThumbs.jsm", "PageThumbs");
},
uninit: function Thumbnails_uninit() {
gBrowser.removeTabsProgressListener(this);
this._tabEvents.forEach(function (aEvent) {
gBrowser.tabContainer.removeEventListener(aEvent, this, false);
}, this);
this._timeouts = null;
this._pageThumbs = null;
},
handleEvent: function Thumbnails_handleEvent(aEvent) {
switch (aEvent.type) {
case "TabSelect":
this._delayedCapture(aEvent.target.linkedBrowser);
break;
case "TabClose": {
let browser = aEvent.target.linkedBrowser;
if (this._timeouts.has(browser)) {
clearTimeout(this._timeouts.get(browser));
this._timeouts.delete(browser);
}
break;
}
}
},
/**
* State change progress listener for all tabs.
*/
onStateChange: function Thumbnails_onStateChange(aBrowser, aWebProgress,
aRequest, aStateFlags, aStatus) {
if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)
this._delayedCapture(aBrowser);
},
_capture: function Thumbnails_capture(aBrowser) {
if (this._shouldCapture(aBrowser)) {
let canvas = this._pageThumbs.capture(aBrowser.contentWindow);
this._pageThumbs.store(aBrowser.currentURI.spec, canvas);
}
},
_delayedCapture: function Thumbnails_delayedCapture(aBrowser) {
if (this._timeouts.has(aBrowser))
clearTimeout(this._timeouts.get(aBrowser));
let timeout = setTimeout(function () {
this._timeouts.delete(aBrowser);
this._capture(aBrowser);
}.bind(this), this._captureDelayMS);
this._timeouts.set(aBrowser, timeout);
},
_shouldCapture: function Thumbnails_shouldCapture(aBrowser) {
// 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;
let channel = aBrowser.docShell.currentDocumentChannel;
try {
// If the channel is a nsIHttpChannel get its http status code.
let httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
// Continue only if we have a 2xx status code.
return Math.floor(httpChannel.responseStatus / 100) == 2;
} catch (e) {
// Not a http channel, we just assume a success status code.
return true;
}
}
};

View File

@ -196,6 +196,7 @@ let gInitialPages = [
#include browser-places.js
#include browser-tabPreviews.js
#include browser-tabview.js
#include browser-thumbnails.js
#ifdef MOZ_SERVICES_SYNC
#include browser-syncui.js
@ -1699,6 +1700,7 @@ function delayedStartup(isLoadingBlank, mustLoadSidebar) {
gSyncUI.init();
#endif
gBrowserThumbnails.init();
TabView.init();
setUrlAndSearchBarWidthForConditionalForwardButton();
@ -1820,6 +1822,7 @@ function BrowserShutdown() {
gPrefService.removeObserver(allTabs.prefName, allTabs);
ctrlTab.uninit();
TabView.uninit();
gBrowserThumbnails.uninit();
try {
FullZoom.destroy();

View File

@ -71,6 +71,7 @@ PARALLEL_DIRS = \
shell \
sidebar \
tabview \
thumbnails \
migration \
$(NULL)

View File

@ -0,0 +1,2 @@
component {5a4ae9b5-f475-48ae-9dce-0b4c1d347884} PageThumbsProtocol.js
contract @mozilla.org/network/protocol;1?name=moz-page-thumb {5a4ae9b5-f475-48ae-9dce-0b4c1d347884}

View File

@ -0,0 +1,23 @@
# 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/.
DEPTH = ../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
EXTRA_COMPONENTS = \
BrowserPageThumbs.manifest \
PageThumbsProtocol.js \
$(NULL)
ifdef ENABLE_TESTS
DIRS += test
endif
include $(topsrcdir)/config/rules.mk
XPIDL_FLAGS += -I$(topsrcdir)/browser/components/

View File

@ -0,0 +1,448 @@
/* 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/. */
/**
* PageThumbsProtocol.js
*
* This file implements the moz-page-thumb:// protocol and the corresponding
* channel delivering cached thumbnails.
*
* URL structure:
*
* moz-page-thumb://thumbnail?url=http%3A%2F%2Fwww.mozilla.org%2F
*
* This URL requests an image for 'http://www.mozilla.org/'.
*/
"use strict";
const Cu = Components.utils;
const Cc = Components.classes;
const Cr = Components.results;
const Ci = Components.interfaces;
Cu.import("resource:///modules/PageThumbs.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
/**
* Implements the thumbnail protocol handler responsible for moz-page-thumb: URIs.
*/
function Protocol() {
}
Protocol.prototype = {
/**
* The scheme used by this protocol.
*/
get scheme() PageThumbs.scheme,
/**
* The default port for this protocol (we don't support ports).
*/
get defaultPort() -1,
/**
* The flags specific to this protocol implementation.
*/
get protocolFlags() {
return Ci.nsIProtocolHandler.URI_IS_UI_RESOURCE |
Ci.nsIProtocolHandler.URI_NORELATIVE |
Ci.nsIProtocolHandler.URI_NOAUTH;
},
/**
* Creates a new URI object that is suitable for loading by this protocol.
* @param aSpec The URI string in UTF8 encoding.
* @param aOriginCharset The charset of the document from which the URI originated.
* @return The newly created URI.
*/
newURI: function Proto_newURI(aSpec, aOriginCharset) {
let uri = Cc["@mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
uri.spec = aSpec;
return uri;
},
/**
* Constructs a new channel from the given URI for this protocol handler.
* @param aURI The URI for which to construct a channel.
* @return The newly created channel.
*/
newChannel: function Proto_newChannel(aURI) {
return new Channel(aURI);
},
/**
* Decides whether to allow a blacklisted port.
* @return Always false, we'll never allow ports.
*/
allowPort: function () false,
classID: Components.ID("{5a4ae9b5-f475-48ae-9dce-0b4c1d347884}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler])
};
let NSGetFactory = XPCOMUtils.generateNSGetFactory([Protocol]);
/**
* A channel implementation responsible for delivering cached thumbnails.
*/
function Channel(aURI) {
this._uri = aURI;
// nsIChannel
this.originalURI = aURI;
// nsIHttpChannel
this._responseHeaders = {"content-type": PageThumbs.contentType};
}
Channel.prototype = {
/**
* Tracks if the channel has been opened, yet.
*/
_wasOpened: false,
/**
* Opens this channel asynchronously.
* @param aListener The listener that receives the channel data when available.
* @param aContext A custom context passed to the listener's methods.
*/
asyncOpen: function Channel_asyncOpen(aListener, aContext) {
if (this._wasOpened)
throw Cr.NS_ERROR_ALREADY_OPENED;
if (this.canceled)
return;
this._listener = aListener;
this._context = aContext;
this._isPending = true;
this._wasOpened = true;
// Try to read the data from the thumbnail cache.
this._readCache(function (aData) {
// Update response if there's no data.
if (!aData) {
this._responseStatus = 404;
this._responseText = "Not Found";
}
this._startRequest();
if (!this.canceled) {
this._addToLoadGroup();
if (aData)
this._serveData(aData);
if (!this.canceled)
this._stopRequest();
}
}.bind(this));
},
/**
* Reads a data stream from the cache entry.
* @param aCallback The callback the data is passed to.
*/
_readCache: function Channel_readCache(aCallback) {
let {url} = parseURI(this._uri);
// Return early if there's no valid URL given.
if (!url) {
aCallback(null);
return;
}
// Try to get a cache entry.
PageThumbsCache.getReadEntry(url, function (aEntry) {
let inputStream = aEntry && aEntry.openInputStream(0);
function closeEntryAndFinish(aData) {
if (aEntry) {
aEntry.close();
}
aCallback(aData);
}
// Check if we have a valid entry and if it has any data.
if (!inputStream || !inputStream.available()) {
closeEntryAndFinish();
return;
}
try {
// Read the cache entry's data.
NetUtil.asyncFetch(inputStream, function (aData, aStatus) {
// We might have been canceled while waiting.
if (this.canceled)
return;
// Check if we have a valid data stream.
if (!Components.isSuccessCode(aStatus) || !aData.available())
aData = null;
closeEntryAndFinish(aData);
}.bind(this));
} catch (e) {
closeEntryAndFinish();
}
}.bind(this));
},
/**
* Calls onStartRequest on the channel listener.
*/
_startRequest: function Channel_startRequest() {
try {
this._listener.onStartRequest(this, this._context);
} catch (e) {
// The listener might throw if the request has been canceled.
this.cancel(Cr.NS_BINDING_ABORTED);
}
},
/**
* Calls onDataAvailable on the channel listener and passes the data stream.
* @param aData The data to be delivered.
*/
_serveData: function Channel_serveData(aData) {
try {
let available = aData.available();
this._listener.onDataAvailable(this, this._context, aData, 0, available);
} catch (e) {
// The listener might throw if the request has been canceled.
this.cancel(Cr.NS_BINDING_ABORTED);
}
},
/**
* Calls onStopRequest on the channel listener.
*/
_stopRequest: function Channel_stopRequest() {
try {
this._listener.onStopRequest(this, this._context, this.status);
} catch (e) {
// This might throw but is generally ignored.
}
// The request has finished, clean up after ourselves.
this._cleanup();
},
/**
* Adds this request to the load group, if any.
*/
_addToLoadGroup: function Channel_addToLoadGroup() {
if (this.loadGroup)
this.loadGroup.addRequest(this, this._context);
},
/**
* Removes this request from its load group, if any.
*/
_removeFromLoadGroup: function Channel_removeFromLoadGroup() {
if (!this.loadGroup)
return;
try {
this.loadGroup.removeRequest(this, this._context, this.status);
} catch (e) {
// This might throw but is ignored.
}
},
/**
* Cleans up the channel when the request has finished.
*/
_cleanup: function Channel_cleanup() {
this._removeFromLoadGroup();
this.loadGroup = null;
this._isPending = false;
delete this._listener;
delete this._context;
},
/* :::::::: nsIChannel ::::::::::::::: */
contentType: PageThumbs.contentType,
contentLength: -1,
owner: null,
contentCharset: null,
notificationCallbacks: null,
get URI() this._uri,
get securityInfo() null,
/**
* Opens this channel synchronously. Not supported.
*/
open: function Channel_open() {
// Synchronous data delivery is not implemented.
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
/* :::::::: nsIHttpChannel ::::::::::::::: */
redirectionLimit: 10,
requestMethod: "GET",
allowPipelining: true,
referrer: null,
get requestSucceeded() true,
_responseStatus: 200,
get responseStatus() this._responseStatus,
_responseText: "OK",
get responseStatusText() this._responseText,
/**
* Checks if the server sent the equivalent of a "Cache-control: no-cache"
* response header.
* @return Always false.
*/
isNoCacheResponse: function () false,
/**
* Checks if the server sent the equivalent of a "Cache-control: no-cache"
* response header.
* @return Always false.
*/
isNoStoreResponse: function () false,
/**
* Returns the value of a particular request header. Not implemented.
*/
getRequestHeader: function Channel_getRequestHeader() {
throw Cr.NS_ERROR_NOT_AVAILABLE;
},
/**
* This method is called to set the value of a particular request header.
* Not implemented.
*/
setRequestHeader: function Channel_setRequestHeader() {
if (this._wasOpened)
throw Cr.NS_ERROR_IN_PROGRESS;
},
/**
* Call this method to visit all request headers. Not implemented.
*/
visitRequestHeaders: function () {},
/**
* Gets the value of a particular response header.
* @param aHeader The case-insensitive name of the response header to query.
* @return The header value.
*/
getResponseHeader: function Channel_getResponseHeader(aHeader) {
let name = aHeader.toLowerCase();
if (name in this._responseHeaders)
return this._responseHeaders[name];
throw Cr.NS_ERROR_NOT_AVAILABLE;
},
/**
* This method is called to set the value of a particular response header.
* @param aHeader The case-insensitive name of the response header to query.
* @param aValue The response header value to set.
*/
setResponseHeader: function Channel_setResponseHeader(aHeader, aValue, aMerge) {
let name = aHeader.toLowerCase();
if (!aValue && !aMerge)
delete this._responseHeaders[name];
else
this._responseHeaders[name] = aValue;
},
/**
* Call this method to visit all response headers.
* @param aVisitor The header visitor.
*/
visitResponseHeaders: function Channel_visitResponseHeaders(aVisitor) {
for (let name in this._responseHeaders) {
let value = this._responseHeaders[name];
try {
aVisitor.visitHeader(name, value);
} catch (e) {
// The visitor can throw to stop the iteration.
return;
}
}
},
/* :::::::: nsIRequest ::::::::::::::: */
loadFlags: Ci.nsIRequest.LOAD_NORMAL,
loadGroup: null,
get name() this._uri.spec,
_status: Cr.NS_OK,
get status() this._status,
_isPending: false,
isPending: function () this._isPending,
resume: function () {},
suspend: function () {},
/**
* Cancels this request.
* @param aStatus The reason for cancelling.
*/
cancel: function Channel_cancel(aStatus) {
if (this.canceled)
return;
this._isCanceled = true;
this._status = aStatus;
this._cleanup();
},
/* :::::::: nsIHttpChannelInternal ::::::::::::::: */
documentURI: null,
_isCanceled: false,
get canceled() this._isCanceled,
QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel,
Ci.nsIHttpChannel,
Ci.nsIHttpChannelInternal,
Ci.nsIRequest])
};
/**
* Parses a given URI and extracts all parameters relevant to this protocol.
* @param aURI The URI to parse.
* @return The parsed parameters.
*/
function parseURI(aURI) {
let {scheme, staticHost} = PageThumbs;
let re = new RegExp("^" + scheme + "://" + staticHost + ".*?\\?");
let query = aURI.spec.replace(re, "");
let params = {};
query.split("&").forEach(function (aParam) {
let [key, value] = aParam.split("=").map(decodeURIComponent);
params[key.toLowerCase()] = value;
});
return params;
}

View File

@ -0,0 +1,21 @@
# 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/.
DEPTH = ../../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
relativesrcdir = browser/components/thumbnails/test
include $(DEPTH)/config/autoconf.mk
include $(topsrcdir)/config/rules.mk
_BROWSER_FILES = \
browser_thumbnails_cache.js \
browser_thumbnails_capture.js \
head.js \
$(NULL)
libs:: $(_BROWSER_FILES)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)

View File

@ -0,0 +1,34 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* These tests ensure that saving a thumbnail to the cache works. They also
* retrieve the thumbnail and display it using an <img> element to compare
* its pixel colors.
*/
function runTests() {
// Create a new tab with a red background.
yield addTab("data:text/html,<body bgcolor=ff0000></body>");
let cw = gBrowser.selectedTab.linkedBrowser.contentWindow;
// Capture a thumbnail for the tab.
let canvas = PageThumbs.capture(cw);
// Store the tab into the thumbnail cache.
yield PageThumbs.store("key", canvas, next);
let {width, height} = canvas;
let thumb = PageThumbs.getThumbnailURL("key", width, height);
// Create a new tab with an image displaying the previously stored thumbnail.
yield addTab("data:text/html,<img src='" + thumb + "'/>" +
"<canvas width=" + width + " height=" + height + "/>");
cw = gBrowser.selectedTab.linkedBrowser.contentWindow;
let [img, canvas] = cw.document.querySelectorAll("img, canvas");
// Draw the image to a canvas and compare the pixel color values.
let ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);
checkCanvasColor(ctx, 255, 0, 0, "we have a red image and canvas");
}

View File

@ -0,0 +1,38 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* These tests ensure that capturing a site's screenshot to a canvas actually
* works.
*/
function runTests() {
// Create a tab with a red background.
yield addTab("data:text/html,<body bgcolor=ff0000></body>");
checkCurrentThumbnailColor(255, 0, 0, "we have a red thumbnail");
// Load a page with a green background.
yield navigateTo("data:text/html,<body bgcolor=00ff00></body>");
checkCurrentThumbnailColor(0, 255, 0, "we have a green thumbnail");
// Load a page with a blue background.
yield navigateTo("data:text/html,<body bgcolor=0000ff></body>");
checkCurrentThumbnailColor(0, 0, 255, "we have a blue thumbnail");
}
/**
* Captures a thumbnail of the currently selected tab and checks the color of
* the resulting canvas.
* @param aRed The red component's intensity.
* @param aGreen The green component's intensity.
* @param aBlue The blue component's intensity.
* @param aMessage The info message to print when checking the pixel color.
*/
function checkCurrentThumbnailColor(aRed, aGreen, aBlue, aMessage) {
let tab = gBrowser.selectedTab;
let cw = tab.linkedBrowser.contentWindow;
let canvas = PageThumbs.capture(cw);
let ctx = canvas.getContext("2d");
checkCanvasColor(ctx, aRed, aGreen, aBlue, aMessage);
}

View File

@ -0,0 +1,93 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource:///modules/PageThumbs.jsm");
registerCleanupFunction(function () {
while (gBrowser.tabs.length > 1)
gBrowser.removeTab(gBrowser.tabs[1]);
});
/**
* Provide the default test function to start our test runner.
*/
function test() {
TestRunner.run();
}
/**
* The test runner that controls the execution flow of our tests.
*/
let TestRunner = {
/**
* Starts the test runner.
*/
run: function () {
waitForExplicitFinish();
this._iter = runTests();
this.next();
},
/**
* Runs the next available test or finishes if there's no test left.
*/
next: function () {
try {
TestRunner._iter.next();
} catch (e if e instanceof StopIteration) {
finish();
}
}
};
/**
* Continues the current test execution.
*/
function next() {
TestRunner.next();
}
/**
* Creates a new tab with the given URI.
* @param aURI The URI that's loaded in the tab.
*/
function addTab(aURI) {
let tab = gBrowser.selectedTab = gBrowser.addTab(aURI);
whenBrowserLoaded(tab.linkedBrowser);
}
/**
* Loads a new URI into the currently selected tab.
* @param aURI The URI to load.
*/
function navigateTo(aURI) {
let browser = gBrowser.selectedTab.linkedBrowser;
whenBrowserLoaded(browser);
browser.loadURI(aURI);
}
/**
* Continues the current test execution when a load event for the given browser
* has been received
* @param aBrowser The browser to listen on.
*/
function whenBrowserLoaded(aBrowser) {
aBrowser.addEventListener("load", function onLoad() {
aBrowser.removeEventListener("load", onLoad, true);
executeSoon(next);
}, true);
}
/**
* Checks the top-left pixel of a given canvas' 2d context for a given color.
* @param aContext The 2D context of a canvas.
* @param aRed The red component's intensity.
* @param aGreen The green component's intensity.
* @param aBlue The blue component's intensity.
* @param aMessage The info message to print when comparing the pixel color.
*/
function checkCanvasColor(aContext, aRed, aGreen, aBlue, aMessage) {
let [r, g, b] = aContext.getImageData(0, 0, 1, 1).data;
ok(r == aRed && g == aGreen && b == aBlue, aMessage);
}

View File

@ -59,6 +59,23 @@ const UPDATE_STYLESHEET_THROTTLE_DELAY = 500;
// @see StyleEditor._persistExpando
const STYLESHEET_EXPANDO = "-moz-styleeditor-stylesheet-";
const TRANSITIONS_PREF = "devtools.styleeditor.transitions";
const TRANSITION_CLASS = "moz-styleeditor-transitioning";
const TRANSITION_DURATION_MS = 500;
const TRANSITION_RULE = "\
:root.moz-styleeditor-transitioning, :root.moz-styleeditor-transitioning * {\
-moz-transition-duration: " + TRANSITION_DURATION_MS + "ms !important; \
-moz-transition-delay: 0ms !important;\
-moz-transition-timing-function: ease-out !important;\
-moz-transition-property: all !important;\
}";
/**
* Style Editor module-global preferences
*/
const TRANSITIONS_ENABLED = Services.prefs.getBoolPref(TRANSITIONS_PREF);
/**
* StyleEditor constructor.
@ -107,6 +124,9 @@ function StyleEditor(aDocument, aStyleSheet)
// this is to perform pending updates before editor closing
this._onWindowUnloadBinding = this._onWindowUnload.bind(this);
this._transitionRefCount = 0;
this._focusOnSourceEditorReady = false;
}
@ -405,8 +425,13 @@ StyleEditor.prototype = {
* Arguments: (StyleEditor editor)
* @see inputElement
*
* onCommit: Called when changes have been committed/applied
* to the live DOM style sheet.
* onUpdate: Called when changes are being applied to the live
* DOM style sheet but might not be complete from
* a WYSIWYG perspective (eg. transitioned update).
* Arguments: (StyleEditor editor)
*
* onCommit: Called when changes have been completely committed
* /applied to the live DOM style sheet.
* Arguments: (StyleEditor editor)
* }
*
@ -640,15 +665,50 @@ StyleEditor.prototype = {
let source = this._state.text;
let oldNode = this.styleSheet.ownerNode;
let oldIndex = this.styleSheetIndex;
let newNode = this.contentDocument.createElement("style");
let content = this.contentDocument;
let newNode = content.createElement("style");
newNode.setAttribute("type", "text/css");
newNode.appendChild(this.contentDocument.createTextNode(source));
newNode.appendChild(content.createTextNode(source));
oldNode.parentNode.replaceChild(newNode, oldNode);
this._styleSheet = this.contentDocument.styleSheets[oldIndex];
this._styleSheet = content.styleSheets[oldIndex];
this._persistExpando();
if (!TRANSITIONS_ENABLED) {
this._triggerAction("Update");
this._triggerAction("Commit");
return;
}
// Insert the global transition rule
// Use a ref count to make sure we do not add it multiple times.. and remove
// it only when all pending StyleEditor-generated transitions ended.
if (!this._transitionRefCount) {
this._styleSheet.insertRule(TRANSITION_RULE, 0);
content.documentElement.classList.add(TRANSITION_CLASS);
}
this._transitionRefCount++;
// Set up clean up and commit after transition duration (+10% buffer)
// @see _onTransitionEnd
content.defaultView.setTimeout(this._onTransitionEnd.bind(this),
Math.floor(TRANSITION_DURATION_MS * 1.1));
this._triggerAction("Update");
},
/**
* This cleans up class and rule added for transition effect and then trigger
* Commit as the changes have been completed.
*/
_onTransitionEnd: function SE__onTransitionEnd()
{
if (--this._transitionRefCount == 0) {
this.contentDocument.documentElement.classList.remove(TRANSITION_CLASS);
this.styleSheet.deleteRule(0);
}
this._triggerAction("Commit");
},

View File

@ -156,11 +156,14 @@ StyleEditorChrome.prototype = {
aContentWindow.addEventListener("unload", onContentUnload, false);
if (aContentWindow.document.readyState == "complete") {
this._root.classList.remove("loading");
this._populateChrome();
return;
} else {
this._root.classList.add("loading");
let onContentReady = function () {
aContentWindow.removeEventListener("load", onContentReady, false);
this._root.classList.remove("loading");
this._populateChrome();
}.bind(this);
aContentWindow.addEventListener("load", onContentReady, false);
@ -299,7 +302,7 @@ StyleEditorChrome.prototype = {
},
/**
* Reset the chrome UI to an empty state.
* Reset the chrome UI to an empty and ready state.
*/
_resetChrome: function SEC__resetChrome()
{
@ -309,6 +312,12 @@ StyleEditorChrome.prototype = {
this._editors = [];
this._view.removeAll();
// (re)enable UI
let matches = this._root.querySelectorAll("toolbarbutton,input,select");
for (let i = 0; i < matches.length; ++i) {
matches[i].removeAttribute("disabled");
}
},
/**

View File

@ -46,6 +46,10 @@ box,
-moz-box-pack: center;
}
.loading .splitview-nav-container > .placeholder {
display: none !important;
}
.splitview-controller,
.splitview-main {
-moz-box-flex: 0;

View File

@ -54,18 +54,20 @@
persist="screenX screenY width height sizemode">
<xul:script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
<xul:box id="style-editor-chrome" class="splitview-root">
<xul:box id="style-editor-chrome" class="splitview-root loading">
<xul:box class="splitview-controller" id="stylesheets-controller" persist="width height">
<xul:box class="splitview-main">
<xul:toolbar class="devtools-toolbar">
<xul:toolbarbutton class="style-editor-newButton devtools-toolbarbutton"
accesskey="&newButton.accesskey;"
tooltiptext="&newButton.tooltip;"
label="&newButton.label;"/>
label="&newButton.label;"
disabled="true"/>
<xul:toolbarbutton class="style-editor-importButton devtools-toolbarbutton"
accesskey="&importButton.accesskey;"
tooltiptext="&importButton.tooltip;"
label="&importButton.label;"/>
label="&importButton.label;"
disabled="true"/>
<xul:spacer flex="1"/>
<xul:textbox class="splitview-filter devtools-searchinput"
type="search" flex="1"

View File

@ -18,6 +18,18 @@ function test()
isnot(gBrowser.selectedBrowser.contentWindow.document.readyState, "complete",
"content document is still loading");
let root = gChromeWindow.document.querySelector(".splitview-root");
ok(root.classList.contains("loading"),
"style editor root element has 'loading' class name");
let button = gChromeWindow.document.querySelector(".style-editor-newButton");
ok(button.hasAttribute("disabled"),
"new style sheet button is disabled");
button = gChromeWindow.document.querySelector(".style-editor-importButton");
ok(button.hasAttribute("disabled"),
"import button is disabled");
if (!aChrome.isContentAttached) {
aChrome.addChromeListener({
onContentAttach: run
@ -35,5 +47,17 @@ function run(aChrome)
is(aChrome.contentWindow.document.readyState, "complete",
"content document is complete");
let root = gChromeWindow.document.querySelector(".splitview-root");
ok(!root.classList.contains("loading"),
"style editor root element does not have 'loading' class name anymore");
let button = gChromeWindow.document.querySelector(".style-editor-newButton");
ok(!button.hasAttribute("disabled"),
"new style sheet button is enabled");
button = gChromeWindow.document.querySelector(".style-editor-importButton");
ok(!button.hasAttribute("disabled"),
"import button is enabled");
finish();
}

View File

@ -4,6 +4,8 @@
const TESTCASE_URI = TEST_BASE + "simple.html";
const TRANSITION_CLASS = "moz-styleeditor-transitioning";
function test()
{
@ -30,6 +32,7 @@ function run(aChrome)
let gAddedCount = 0; // to add new stylesheet after the 2 initial stylesheets
let gNewEditor; // to make sure only one new stylesheet got created
let gUpdateCount = 0; // to make sure only one Update event is triggered
let gCommitCount = 0; // to make sure only one Commit event is triggered
function testEditorAdded(aChrome, aEditor)
@ -82,6 +85,13 @@ function testEditorAdded(aChrome, aEditor)
}, gChromeWindow) ;
},
onUpdate: function (aEditor) {
gUpdateCount++;
ok(content.document.documentElement.classList.contains(TRANSITION_CLASS),
"StyleEditor's transition class has been added to content");
},
onCommit: function (aEditor) {
gCommitCount++;
@ -99,7 +109,11 @@ function testEditorAdded(aChrome, aEditor)
is(computedStyle.backgroundColor, "rgb(255, 0, 0)",
"content's background color has been updated to red");
ok(!content.document.documentElement.classList.contains(TRANSITION_CLASS),
"StyleEditor's transition class has been removed from content");
executeSoon(function () {
is(gUpdateCount, 1, "received only one Update event (throttle)");
is(gCommitCount, 1, "received only one Commit event (throttle)");
aEditor.removeActionListener(listener);

View File

@ -37,7 +37,7 @@
*
* ***** END LICENSE BLOCK ***** */
const TEST_REPLACED_API_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-console-replaced-api.html";
const TEST_REPLACED_API_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-replaced-api.html";
function test() {
waitForExplicitFinish();

View File

@ -41,7 +41,7 @@
// Tests that the page's resources are displayed in the console as they're
// loaded
const TEST_NETWORK_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-network.html" + "?_date=" + Date.now();
const TEST_NETWORK_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-network.html" + "?_date=" + Date.now();
function test() {
addTab("data:text/html,Web Console basic network logging test");

View File

@ -39,7 +39,7 @@
* ***** END LICENSE BLOCK ***** */
// Tests that the console object still exists after a page reload.
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-console.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
function test() {
addTab(TEST_URI);

View File

@ -40,7 +40,7 @@
// Tests that errors still show up in the Web Console after a page reload.
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-error.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-error.html";
function test() {
expectUncaughtException();

View File

@ -12,7 +12,7 @@
Cu.import("resource:///modules/HUDService.jsm");
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-console.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
function test() {
addTab(TEST_URI);

View File

@ -10,7 +10,7 @@
// Tests that the Web Console close button functions.
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-console.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
function test() {
addTab(TEST_URI);

View File

@ -12,7 +12,7 @@
// Tests that the Web Console limits the number of lines displayed according to
// the user's preferences.
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-console.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
function test() {
addTab(TEST_URI);

View File

@ -36,7 +36,7 @@
*
* ***** END LICENSE BLOCK ***** */
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-bug-585956-console-trace.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-585956-console-trace.html";
function test() {
addTab(TEST_URI);

View File

@ -40,7 +40,7 @@
// Tests that adding text to one of the output labels doesn't cause errors.
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-console.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
function test() {
addTab(TEST_URI);

View File

@ -36,11 +36,11 @@
*
* ***** END LICENSE BLOCK ***** */
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-bug-593003-iframe-wrong-hud.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-593003-iframe-wrong-hud.html";
const TEST_IFRAME_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-bug-593003-iframe-wrong-hud-iframe.html";
const TEST_IFRAME_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-593003-iframe-wrong-hud-iframe.html";
const TEST_DUMMY_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-console.html";
const TEST_DUMMY_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
let tab1, tab2;

View File

@ -8,7 +8,7 @@
*
* ***** END LICENSE BLOCK ***** */
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-console.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
let tab1, tab2, win1, win2;
let noErrors = true;

View File

@ -8,7 +8,7 @@
*
* ***** END LICENSE BLOCK ***** */
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-network.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-network.html";
function tabLoad(aEvent) {
browser.removeEventListener(aEvent.type, arguments.callee, true);

View File

@ -8,7 +8,7 @@
*
* ***** END LICENSE BLOCK ***** */
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-bug-597756-reopen-closed-tab.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-597756-reopen-closed-tab.html";
let newTabIsOpen = false;

View File

@ -8,7 +8,7 @@
*
* ***** END LICENSE BLOCK ***** */
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-bug-599725-response-headers.sjs";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-599725-response-headers.sjs";
let lastFinishedRequest = null;

View File

@ -8,7 +8,7 @@
*
* ***** END LICENSE BLOCK ***** */
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-bug-600183-charset.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-600183-charset.html";
let lastFinishedRequest = null;

View File

@ -8,7 +8,7 @@
*
* ***** END LICENSE BLOCK ***** */
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-bug-601177-log-levels.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-601177-log-levels.html";
let msgs;

View File

@ -8,7 +8,7 @@
*
* ***** END LICENSE BLOCK ***** */
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-bug-603750-websocket.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-603750-websocket.html";
const pref_ws = "network.websocket.enabled";
const pref_block = "network.websocket.override-security-block";

View File

@ -8,7 +8,7 @@
*
* ***** END LICENSE BLOCK ***** */
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-bug-613013-console-api-iframe.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-613013-console-api-iframe.html";
let TestObserver = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),

View File

@ -7,7 +7,7 @@
* Mihai Sucan <mihai.sucan@gmail.com>
*/
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-bug-630733-response-redirect-headers.sjs";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-630733-response-redirect-headers.sjs";
let lastFinishedRequests = {};

View File

@ -4,9 +4,9 @@
// Tests that network log messages bring up the network panel.
const TEST_NETWORK_REQUEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-network-request.html";
const TEST_NETWORK_REQUEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-network-request.html";
const TEST_IMG = "http://example.com/browser/browser/devtools/webconsole/test//test-image.png";
const TEST_IMG = "http://example.com/browser/browser/devtools/webconsole/test/test-image.png";
const TEST_DATA_JSON_CONTENT =
'{ id: "test JSON data", myArray: [ "foo", "bar", "baz", "biff" ] }';

View File

@ -37,7 +37,7 @@
// Tests that the basic console.log()-style APIs and filtering work.
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-console-extras.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-extras.html";
function test() {
addTab(TEST_URI);

View File

@ -40,7 +40,7 @@
// Tests that the message type filter checkboxes work.
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-console.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
function test() {
addTab(TEST_URI);

View File

@ -40,7 +40,7 @@
// Tests that the text filter box works.
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-console.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
function test() {
addTab(TEST_URI);

View File

@ -41,7 +41,7 @@
// Tests that console logging via the console API produces nodes of the correct
// CSS classes.
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-console.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
function test() {
addTab(TEST_URI);

View File

@ -35,7 +35,7 @@
*
* ***** END LICENSE BLOCK ***** */
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-console.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
function test() {
addTab(TEST_URI);

View File

@ -12,9 +12,9 @@
// Tests that network log messages bring up the network panel.
const TEST_NETWORK_REQUEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-network-request.html";
const TEST_NETWORK_REQUEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-network-request.html";
const TEST_IMG = "http://example.com/browser/browser/devtools/webconsole/test//test-image.png";
const TEST_IMG = "http://example.com/browser/browser/devtools/webconsole/test/test-image.png";
const TEST_DATA_JSON_CONTENT =
'{ id: "test JSON data", myArray: [ "foo", "bar", "baz", "biff" ] }';

View File

@ -41,9 +41,9 @@
// Tests that the network panel works.
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-console.html";
const TEST_IMG = "http://example.com/browser/browser/devtools/webconsole/test//test-image.png";
const TEST_ENCODING_ISO_8859_1 = "http://example.com/browser/browser/devtools/webconsole/test//test-encoding-ISO-8859-1.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
const TEST_IMG = "http://example.com/browser/browser/devtools/webconsole/test/test-image.png";
const TEST_ENCODING_ISO_8859_1 = "http://example.com/browser/browser/devtools/webconsole/test/test-encoding-ISO-8859-1.html";
let testDriver;

View File

@ -41,7 +41,7 @@
// Tests that the HUD service keeps an accurate registry of all the Web Console
// instances.
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-console.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
function test() {
addTab(TEST_URI);

View File

@ -4,7 +4,7 @@
// Tests that source URLs in the Web Console can be clicked to display the
// standard View Source window.
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-error.html";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-error.html";
function test() {
expectUncaughtException();

View File

@ -8,6 +8,6 @@
<body>
<p>WebConsole test: iframe associated to the wrong HUD.</p>
<iframe
src="http://example.com/browser/browser/devtools/webconsole/test//test-bug-593003-iframe-wrong-hud-iframe.html"></iframe>
src="http://example.com/browser/browser/devtools/webconsole/test/test-bug-593003-iframe-wrong-hud-iframe.html"></iframe>
</body>
</html>

View File

@ -27,7 +27,7 @@
<h1>Heads Up Display HTTP Logging Testpage</h1>
<h2>This page is used to test the HTTP logging.</h2>
<form action="http://example.com/browser/browser/devtools/webconsole/test//test-network-request.html" method="post">
<form action="http://example.com/browser/browser/devtools/webconsole/test/test-network-request.html" method="post">
<input name="name" type="text" value="foo bar"><br>
<input name="age" type="text" value="144"><br>
</form>

View File

@ -283,6 +283,7 @@
@BINPATH@/components/nsSetDefaultBrowser.manifest
@BINPATH@/components/nsSetDefaultBrowser.js
@BINPATH@/components/BrowserPlaces.manifest
@BINPATH@/components/BrowserPageThumbs.manifest
@BINPATH@/components/nsPrivateBrowsingService.manifest
@BINPATH@/components/nsPrivateBrowsingService.js
@BINPATH@/components/toolkitsearch.manifest
@ -346,6 +347,7 @@
@BINPATH@/components/nsPlacesExpiration.js
@BINPATH@/components/PlacesProtocolHandler.js
@BINPATH@/components/PlacesCategoriesStarter.js
@BINPATH@/components/PageThumbsProtocol.js
@BINPATH@/components/nsDefaultCLH.manifest
@BINPATH@/components/nsDefaultCLH.js
@BINPATH@/components/nsContentPrefService.manifest

View File

@ -52,6 +52,7 @@ EXTRA_JS_MODULES = \
openLocationLastURL.jsm \
NetworkPrioritizer.jsm \
offlineAppCache.jsm \
PageThumbs.jsm \
$(NULL)
ifeq ($(MOZ_WIDGET_TOOLKIT),windows)

View File

@ -0,0 +1,265 @@
/* 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";
let EXPORTED_SYMBOLS = ["PageThumbs", "PageThumbsCache"];
const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
/**
* The default width for page thumbnails.
*
* Hint: This is the default value because the 'New Tab Page' is the only
* client for now.
*/
const THUMBNAIL_WIDTH = 201;
/**
* The default height for page thumbnails.
*
* Hint: This is the default value because the 'New Tab Page' is the only
* client for now.
*/
const THUMBNAIL_HEIGHT = 127;
/**
* The default background color for page thumbnails.
*/
const THUMBNAIL_BG_COLOR = "#fff";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
/**
* Singleton providing functionality for capturing web page thumbnails and for
* accessing them if already cached.
*/
let PageThumbs = {
/**
* The scheme to use for thumbnail urls.
*/
get scheme() "moz-page-thumb",
/**
* The static host to use for thumbnail urls.
*/
get staticHost() "thumbnail",
/**
* The thumbnails' image type.
*/
get contentType() "image/png",
/**
* Gets the thumbnail image's url for a given web page's url.
* @param aUrl The web page's url that is depicted in the thumbnail.
* @return The thumbnail image's url.
*/
getThumbnailURL: function PageThumbs_getThumbnailURL(aUrl) {
return this.scheme + "://" + this.staticHost +
"?url=" + encodeURIComponent(aUrl);
},
/**
* Creates a canvas containing a thumbnail depicting the given window.
* @param aWindow The DOM window to capture a thumbnail from.
* @return The newly created canvas containing the image data.
*/
capture: function PageThumbs_capture(aWindow) {
let [sx, sy, sw, sh, scale] = this._determineCropRectangle(aWindow);
let canvas = this._createCanvas();
let ctx = canvas.getContext("2d");
// Scale the canvas accordingly.
ctx.scale(scale, scale);
try {
// Draw the window contents to the canvas.
ctx.drawWindow(aWindow, sx, sy, sw, sh, THUMBNAIL_BG_COLOR,
ctx.DRAWWINDOW_DO_NOT_FLUSH);
} catch (e) {
// We couldn't draw to the canvas for some reason.
}
return canvas;
},
/**
* Stores the image data contained in the given canvas to the underlying
* storage.
* @param aKey The key to use for the storage.
* @param aCanvas The canvas containing the thumbnail's image data.
* @param aCallback The function to be called when the canvas data has been
* stored (optional).
*/
store: function PageThumbs_store(aKey, aCanvas, aCallback) {
let self = this;
function finish(aSuccessful) {
if (aCallback)
aCallback(aSuccessful);
}
// Get a writeable cache entry.
PageThumbsCache.getWriteEntry(aKey, function (aEntry) {
if (!aEntry) {
finish(false);
return;
}
// Extract image data from the canvas.
self._readImageData(aCanvas, function (aData) {
let outputStream = aEntry.openOutputStream(0);
// Write the image data to the cache entry.
NetUtil.asyncCopy(aData, outputStream, function (aResult) {
let success = Components.isSuccessCode(aResult);
if (success)
aEntry.markValid();
aEntry.close();
finish(success);
});
});
});
},
/**
* Reads the image data from a given canvas and passes it to the callback.
* @param aCanvas The canvas to read the image data from.
* @param aCallback The function that the image data is passed to.
*/
_readImageData: function PageThumbs_readImageData(aCanvas, aCallback) {
let dataUri = aCanvas.toDataURL(PageThumbs.contentType, "");
let uri = Services.io.newURI(dataUri, "UTF8", null);
NetUtil.asyncFetch(uri, function (aData, aResult) {
if (Components.isSuccessCode(aResult) && aData && aData.available())
aCallback(aData);
});
},
/**
* Determines the crop rectangle for a given content window.
* @param aWindow The content window.
* @return An array containing x, y, width, heigh and the scale of the crop
* rectangle.
*/
_determineCropRectangle: function PageThumbs_determineCropRectangle(aWindow) {
let sx = 0;
let sy = 0;
let sw = aWindow.innerWidth;
let sh = aWindow.innerHeight;
let scale = Math.max(THUMBNAIL_WIDTH / sw, THUMBNAIL_HEIGHT / sh);
let scaledWidth = sw * scale;
let scaledHeight = sh * scale;
if (scaledHeight > THUMBNAIL_HEIGHT) {
sy = Math.floor(Math.abs((scaledHeight - THUMBNAIL_HEIGHT) / 2) / scale);
sh -= 2 * sy;
}
if (scaledWidth > THUMBNAIL_WIDTH) {
sx = Math.floor(Math.abs((scaledWidth - THUMBNAIL_WIDTH) / 2) / scale);
sw -= 2 * sx;
}
return [sx, sy, sw, sh, scale];
},
/**
* Creates a new hidden canvas element.
* @return The newly created canvas.
*/
_createCanvas: function PageThumbs_createCanvas() {
let doc = Services.appShell.hiddenDOMWindow.document;
let canvas = doc.createElementNS(HTML_NAMESPACE, "canvas");
canvas.mozOpaque = true;
canvas.width = THUMBNAIL_WIDTH;
canvas.height = THUMBNAIL_HEIGHT;
return canvas;
}
};
/**
* A singleton handling the storage of page thumbnails.
*/
let PageThumbsCache = {
/**
* Calls the given callback with a cache entry opened for reading.
* @param aKey The key identifying the desired cache entry.
* @param aCallback The callback that is called when the cache entry is ready.
*/
getReadEntry: function Cache_getReadEntry(aKey, aCallback) {
// Try to open the desired cache entry.
this._openCacheEntry(aKey, Ci.nsICache.ACCESS_READ, aCallback);
},
/**
* Calls the given callback with a cache entry opened for writing.
* @param aKey The key identifying the desired cache entry.
* @param aCallback The callback that is called when the cache entry is ready.
*/
getWriteEntry: function Cache_getWriteEntry(aKey, aCallback) {
// Try to open the desired cache entry.
this._openCacheEntry(aKey, Ci.nsICache.ACCESS_WRITE, aCallback);
},
/**
* Opens the cache entry identified by the given key.
* @param aKey The key identifying the desired cache entry.
* @param aAccess The desired access mode (see nsICache.ACCESS_* constants).
* @param aCallback The function to be called when the cache entry was opened.
*/
_openCacheEntry: function Cache_openCacheEntry(aKey, aAccess, aCallback) {
function onCacheEntryAvailable(aEntry, aAccessGranted, aStatus) {
let validAccess = aAccess == aAccessGranted;
let validStatus = Components.isSuccessCode(aStatus);
// Check if a valid entry was passed and if the
// access we requested was actually granted.
if (aEntry && !(validAccess && validStatus)) {
aEntry.close();
aEntry = null;
}
aCallback(aEntry);
}
let listener = this._createCacheListener(onCacheEntryAvailable);
this._cacheSession.asyncOpenCacheEntry(aKey, aAccess, listener);
},
/**
* Returns a cache listener implementing the nsICacheListener interface.
* @param aCallback The callback to be called when the cache entry is available.
* @return The new cache listener.
*/
_createCacheListener: function Cache_createCacheListener(aCallback) {
return {
onCacheEntryAvailable: aCallback,
QueryInterface: XPCOMUtils.generateQI([Ci.nsICacheListener])
};
}
};
/**
* Define a lazy getter for the cache session.
*/
XPCOMUtils.defineLazyGetter(PageThumbsCache, "_cacheSession", function () {
return Services.cache.createSession(PageThumbs.scheme,
Ci.nsICache.STORE_ON_DISK, true);
});

View File

@ -41,6 +41,12 @@
color: white;
}
.loading .splitview-nav-container {
background-image: url(chrome://global/skin/icons/loading_16.png);
background-repeat: no-repeat;
background-position: center center;
}
.splitview-nav {
-moz-appearance: none;
margin: 0;
@ -70,7 +76,6 @@
.placeholder {
-moz-box-flex: 1;
-moz-box-back: center;
text-align: center;
}

View File

@ -36,6 +36,10 @@
*
* ***** END LICENSE BLOCK ***** */
#style-editor-chrome {
background-color: hsl(208,11%,27%);
}
.stylesheet-title,
.stylesheet-name {
text-decoration: none;

View File

@ -41,6 +41,12 @@
color: white;
}
.loading .splitview-nav-container {
background-image: url(chrome://global/skin/icons/loading_16.png);
background-repeat: no-repeat;
background-position: center center;
}
.splitview-nav {
-moz-appearance: none;
margin: 0;
@ -70,7 +76,6 @@
.placeholder {
-moz-box-flex: 1;
-moz-box-back: center;
text-align: center;
}

View File

@ -36,6 +36,10 @@
*
* ***** END LICENSE BLOCK ***** */
#style-editor-chrome {
background-color: hsl(208,11%,27%);
}
.stylesheet-title,
.stylesheet-name {
text-decoration: none;

View File

@ -41,6 +41,12 @@
color: white;
}
.loading .splitview-nav-container {
background-image: url(chrome://global/skin/icons/loading_16.png);
background-repeat: no-repeat;
background-position: center center;
}
.splitview-nav {
-moz-appearance: none;
margin: 0;
@ -70,7 +76,6 @@
.placeholder {
-moz-box-flex: 1;
-moz-box-back: center;
text-align: center;
}

View File

@ -36,6 +36,10 @@
*
* ***** END LICENSE BLOCK ***** */
#style-editor-chrome {
background-color: hsl(211,21%,26%);
}
.stylesheet-title,
.stylesheet-name {
text-decoration: none;

View File

@ -63,6 +63,8 @@ XPCOMUtils.defineLazyGetter(Services, "dirsvc", function () {
});
let initTable = [
["appShell", "@mozilla.org/appshell/appShellService;1", "nsIAppShellService"],
["cache", "@mozilla.org/network/cache-service;1", "nsICacheService"],
["console", "@mozilla.org/consoleservice;1", "nsIConsoleService"],
["contentPrefs", "@mozilla.org/content-pref/service;1", "nsIContentPrefService"],
["cookies", "@mozilla.org/cookiemanager;1", "nsICookieManager2"],