Merge from mozilla-central.
Merge from mozilla-central.
--- a/.hgtags
+++ b/.hgtags
@@ -81,8 +81,9 @@ bbc7014db2de49e2301680d2a86be8a53108a88a
b6627f28b7ec17e1b46a594df0f780d3a40847e4 FIREFOX_AURORA_13_BASE
357da346ceb705d196a46574804c7c4ec44ac186 FIREFOX_AURORA_14_BASE
26dcd1b1a20893ad99341c61c6b1239ff1523858 FIREFOX_AURORA_15_BASE
0accd12a8e7e217836ea3f1ee7c411913fc75d8e FIREFOX_AURORA_16_BASE
0000000000000000000000000000000000000000 FIREFOX_AURORA_16_BASE
9697eadafa13b4e9233b39aaeecfeac79503cb54 FIREFOX_AURORA_16_BASE
9697eadafa13b4e9233b39aaeecfeac79503cb54 FIREFOX_AURORA_16_BASE
6fdf9985acfe6f939da584b2559464ab22264fe7 FIREFOX_AURORA_16_BASE
+fd72dbbd692012224145be1bf13df1d7675fd277 FIREFOX_AURORA_17_BASE
--- a/accessible/src/base/AccEvent.h
+++ b/accessible/src/base/AccEvent.h
@@ -102,17 +102,17 @@ public:
virtual unsigned int GetEventGroups() const
{
return 1U << eGenericEvent;
}
/**
* Reference counting and cycle collection.
*/
- NS_INLINE_DECL_REFCOUNTING(AccEvent)
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AccEvent)
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(AccEvent)
protected:
/**
* Get an accessible from event target node.
*/
Accessible* GetAccessibleForNode() const;
--- a/accessible/src/base/NotificationController.cpp
+++ b/accessible/src/base/NotificationController.cpp
@@ -50,18 +50,18 @@ NotificationController::~NotificationCon
NS_ASSERTION(!mDocument, "Controller wasn't shutdown properly!");
if (mDocument)
Shutdown();
}
////////////////////////////////////////////////////////////////////////////////
// NotificationCollector: AddRef/Release and cycle collection
-NS_IMPL_ADDREF(NotificationController)
-NS_IMPL_RELEASE(NotificationController)
+NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(NotificationController)
+NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(NotificationController)
NS_IMPL_CYCLE_COLLECTION_NATIVE_CLASS(NotificationController)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_NATIVE(NotificationController)
if (tmp->mDocument)
tmp->Shutdown();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
--- a/accessible/src/base/NotificationController.h
+++ b/accessible/src/base/NotificationController.h
@@ -168,17 +168,17 @@ public:
}
#ifdef DEBUG
bool IsUpdating() const
{ return mObservingState == eRefreshProcessingForUpdate; }
#endif
protected:
- nsAutoRefCnt mRefCnt;
+ nsCycleCollectingAutoRefCnt mRefCnt;
NS_DECL_OWNINGTHREAD
/**
* Start to observe refresh to make notifications and events processing after
* layout.
*/
void ScheduleProcessing();
@@ -267,17 +267,17 @@ private:
* Storage for content inserted notification information.
*/
class ContentInsertion
{
public:
ContentInsertion(DocAccessible* aDocument, Accessible* aContainer);
virtual ~ContentInsertion() { mDocument = nullptr; }
- NS_INLINE_DECL_REFCOUNTING(ContentInsertion)
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(ContentInsertion)
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(ContentInsertion)
bool InitChildList(nsIContent* aStartChildNode, nsIContent* aEndChildNode);
void Process();
private:
ContentInsertion();
ContentInsertion(const ContentInsertion&);
--- a/accessible/src/base/RoleAsserts.cpp
+++ b/accessible/src/base/RoleAsserts.cpp
@@ -7,11 +7,13 @@
#include "nsIAccessibleRole.h"
#include "Role.h"
#include "mozilla/Assertions.h"
using namespace mozilla::a11y;
#define ROLE(geckoRole, stringRole, atkRole, macRole, msaaRole, ia2Role, nameRule) \
- MOZ_STATIC_ASSERT(roles::geckoRole == nsIAccessibleRole::ROLE_ ## geckoRole, "internal and xpcom roles differ!");
+ MOZ_STATIC_ASSERT(static_cast<uint32_t>(roles::geckoRole) \
+ == static_cast<uint32_t>(nsIAccessibleRole::ROLE_ ## geckoRole), \
+ "internal and xpcom roles differ!");
#include "RoleMap.h"
#undef ROLE
--- a/accessible/src/generic/Accessible.cpp
+++ b/accessible/src/generic/Accessible.cpp
@@ -794,18 +794,17 @@ Accessible::ChildAtPoint(int32_t aX, int
NS_ENSURE_TRUE(frame, nullptr);
nsPresContext *presContext = frame->PresContext();
nsRect screenRect = frame->GetScreenRectInAppUnits();
nsPoint offset(presContext->DevPixelsToAppUnits(aX) - screenRect.x,
presContext->DevPixelsToAppUnits(aY) - screenRect.y);
- nsIPresShell* presShell = presContext->PresShell();
- nsIFrame *foundFrame = presShell->GetFrameForPoint(frame, offset);
+ nsIFrame *foundFrame = nsLayoutUtils::GetFrameForPoint(frame, offset);
nsIContent* content = nullptr;
if (!foundFrame || !(content = foundFrame->GetContent()))
return fallbackAnswer;
// Get accessible for the node with the point or the first accessible in
// the DOM parent chain.
DocAccessible* contentDocAcc = GetAccService()->
--- a/accessible/src/jsat/TouchAdapter.jsm
+++ b/accessible/src/jsat/TouchAdapter.jsm
@@ -80,48 +80,51 @@ var TouchAdapter = {
if (Utils.OS != 'Android')
Mouse2Touch.detach(aWindow);
delete this.chromeWin;
},
handleEvent: function TouchAdapter_handleEvent(aEvent) {
let touches = aEvent.changedTouches;
+ // XXX: Until bug 77992 is resolved, on desktop we get microseconds
+ // instead of milliseconds.
+ let timeStamp = (Utils.OS == 'Android') ? aEvent.timeStamp : Date.now();
switch (aEvent.type) {
case 'touchstart':
for (var i = 0; i < touches.length; i++) {
let touch = touches[i];
- let touchPoint = new TouchPoint(touch, aEvent.timeStamp, this._dpi);
+ let touchPoint = new TouchPoint(touch, timeStamp, this._dpi);
this._touchPoints[touch.identifier] = touchPoint;
- this._lastExploreTime = aEvent.timeStamp + this.SWIPE_MAX_DURATION;
+ this._lastExploreTime = timeStamp + this.SWIPE_MAX_DURATION;
}
this._dwellTimeout = this.chromeWin.setTimeout(
(function () {
- this.compileAndEmit(aEvent.timeStamp + this.DWELL_THRESHOLD);
+ this.compileAndEmit(timeStamp + this.DWELL_THRESHOLD);
}).bind(this), this.DWELL_THRESHOLD);
break;
case 'touchmove':
for (var i = 0; i < touches.length; i++) {
let touch = touches[i];
let touchPoint = this._touchPoints[touch.identifier];
- touchPoint.update(touch, aEvent.timeStamp);
+ touchPoint.update(touch, timeStamp);
}
- if (aEvent.timeStamp - this._lastExploreTime >= EXPLORE_THROTTLE) {
- this.compileAndEmit(aEvent.timeStamp);
- this._lastExploreTime = aEvent.timeStamp;
+ if (timeStamp - this._lastExploreTime >= EXPLORE_THROTTLE) {
+ this.compileAndEmit(timeStamp);
+ this._lastExploreTime = timeStamp;
}
break;
case 'touchend':
for (var i = 0; i < touches.length; i++) {
let touch = touches[i];
let touchPoint = this._touchPoints[touch.identifier];
- touchPoint.update(touch, aEvent.timeStamp);
+ touchPoint.update(touch, timeStamp);
touchPoint.finish();
}
- this.compileAndEmit(aEvent.timeStamp);
+ this.compileAndEmit(timeStamp);
break;
}
},
cleanupTouches: function cleanupTouches() {
for (var identifier in this._touchPoints) {
if (!this._touchPoints[identifier].done)
continue;
--- a/accessible/src/jsat/Utils.jsm
+++ b/accessible/src/jsat/Utils.jsm
@@ -7,27 +7,33 @@
const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
Cu.import('resource://gre/modules/Services.jsm');
var EXPORTED_SYMBOLS = ['Utils', 'Logger'];
-var gAccRetrieval = Cc['@mozilla.org/accessibleRetrieval;1'].
- getService(Ci.nsIAccessibleRetrieval);
-
var Utils = {
_buildAppMap: {
'{3c2e2abc-06d4-11e1-ac3b-374f68613e61}': 'b2g',
'{ec8030f7-c20a-464f-9b0e-13a3a9e97384}': 'browser',
'{aa3c5121-dab2-40e2-81ca-7ea25febc110}': 'mobile/android',
'{a23983c0-fd0e-11dc-95ff-0800200c9a66}': 'mobile/xul'
},
+ get AccRetrieval() {
+ if (!this._AccRetrieval) {
+ this._AccRetrieval = Cc['@mozilla.org/accessibleRetrieval;1'].
+ getService(Ci.nsIAccessibleRetrieval);
+ }
+
+ return this._AccRetrieval;
+ },
+
get MozBuildApp() {
if (!this._buildApp)
this._buildApp = this._buildAppMap[Services.appinfo.ID];
return this._buildApp;
},
get OS() {
if (!this._OS)
@@ -67,17 +73,17 @@ var Utils = {
getCurrentContentDoc: function getCurrentContentDoc(aWindow) {
if (this.MozBuildApp == "b2g")
return this.getBrowserApp(aWindow).contentBrowser.contentDocument;
return this.getBrowserApp(aWindow).selectedBrowser.contentDocument;
},
getAllDocuments: function getAllDocuments(aWindow) {
- let doc = gAccRetrieval.
+ let doc = this.AccRetrieval.
getAccessibleFor(this.getCurrentContentDoc(aWindow)).
QueryInterface(Ci.nsIAccessibleDocument);
let docs = [];
function getAllDocuments(aDocument) {
docs.push(aDocument.DOMDocument);
for (let i = 0; i < aDocument.childDocumentCount; i++)
getAllDocuments(aDocument.getChildDocumentAt(i));
}
@@ -101,17 +107,17 @@ var Utils = {
let state = {};
let extState = {};
aAccessible.getState(state, extState);
return [state.value, extState.value];
},
getVirtualCursor: function getVirtualCursor(aDocument) {
let doc = (aDocument instanceof Ci.nsIAccessible) ? aDocument :
- gAccRetrieval.getAccessibleFor(aDocument);
+ this.AccRetrieval.getAccessibleFor(aDocument);
while (doc) {
try {
return doc.QueryInterface(Ci.nsIAccessibleCursorable).virtualCursor;
} catch (x) {
doc = doc.parentDocument;
}
}
@@ -165,17 +171,17 @@ var Utils = {
changePage: function changePage(aWindow, aPage) {
for each (let doc in this.getAllDocuments(aWindow)) {
// Get current main section or active target.
let main = doc.querySelector('[role=main]') ||
doc.querySelector(':target');
if (!main)
continue;
- let mainAcc = gAccRetrieval.getAccessibleFor(main);
+ let mainAcc = this.AccRetrieval.getAccessibleFor(main);
if (!mainAcc)
continue;
let controllers = mainAcc.
getRelationByType(Ci.nsIAccessibleRelation.RELATION_CONTROLLED_BY);
for (var i=0; controllers.targetsCount > i; i++) {
let controller = controllers.getTarget(i);
@@ -232,41 +238,41 @@ var Logger = {
error: function error() {
this.log.apply(
this, [this.ERROR].concat(Array.prototype.slice.call(arguments)));
},
accessibleToString: function accessibleToString(aAccessible) {
let str = '[ defunct ]';
try {
- str = '[ ' + gAccRetrieval.getStringRole(aAccessible.role) +
+ str = '[ ' + Utils.AccRetrieval.getStringRole(aAccessible.role) +
' | ' + aAccessible.name + ' ]';
} catch (x) {
}
return str;
},
eventToString: function eventToString(aEvent) {
- let str = gAccRetrieval.getStringEventType(aEvent.eventType);
+ let str = Utils.AccRetrieval.getStringEventType(aEvent.eventType);
if (aEvent.eventType == Ci.nsIAccessibleEvent.EVENT_STATE_CHANGE) {
let event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent);
let stateStrings = (event.isExtraState()) ?
- gAccRetrieval.getStringStates(0, event.state) :
- gAccRetrieval.getStringStates(event.state, 0);
+ Utils.AccRetrieval.getStringStates(0, event.state) :
+ Utils.AccRetrieval.getStringStates(event.state, 0);
str += ' (' + stateStrings.item(0) + ')';
}
return str;
},
statesToString: function statesToString(aAccessible) {
let [state, extState] = Utils.getStates(aAccessible);
let stringArray = [];
- let stateStrings = gAccRetrieval.getStringStates(state, extState);
+ let stateStrings = Utils.AccRetrieval.getStringStates(state, extState);
for (var i=0; i < stateStrings.length; i++)
stringArray.push(stateStrings.item(i));
return stringArray.join(' ');
},
dumpTree: function dumpTree(aLogLevel, aRootAccessible) {
if (aLogLevel < this.logLevel)
return;
--- a/accessible/src/jsat/UtteranceGenerator.jsm
+++ b/accessible/src/jsat/UtteranceGenerator.jsm
@@ -12,21 +12,21 @@ const Cr = Components.results;
const INCLUDE_DESC = 0x01;
const INCLUDE_NAME = 0x02;
const INCLUDE_CUSTOM = 0x04;
var gStringBundle = Cc['@mozilla.org/intl/stringbundle;1'].
getService(Ci.nsIStringBundleService).
createBundle('chrome://global/locale/AccessFu.properties');
-var gAccRetrieval = Cc['@mozilla.org/accessibleRetrieval;1'].
- getService(Ci.nsIAccessibleRetrieval);
var EXPORTED_SYMBOLS = ['UtteranceGenerator'];
+Cu.import('resource://gre/modules/accessibility/Utils.jsm');
+
/**
* Generates speech utterances from objects, actions and state changes.
* An utterance is an array of strings.
*
* It should not be assumed that flattening an utterance array would create a
* gramatically correct sentence. For example, {@link genForObject} might
* return: ['graphic', 'Welcome to my home page'].
* Each string element in an utterance should be gramatically correct in itself.
@@ -61,17 +61,17 @@ var UtteranceGenerator = {
* @param {nsIAccessible} aAccessible accessible object to generate utterance
* for.
* @return {Array} Two string array. The first string describes the object
* and its states. The second string is the object's name. Whether the
* object's description or it's role is included is determined by
* {@link verbosityRoleMap}.
*/
genForObject: function genForObject(aAccessible) {
- let roleString = gAccRetrieval.getStringRole(aAccessible.role);
+ let roleString = Utils.AccRetrieval.getStringRole(aAccessible.role);
let func = this.objectUtteranceFunctions[roleString] ||
this.objectUtteranceFunctions.defaultFunc;
let flags = this.verbosityRoleMap[roleString] || 0;
if (aAccessible.childCount == 0)
flags |= INCLUDE_NAME;
@@ -147,17 +147,17 @@ var UtteranceGenerator = {
'toolbar': INCLUDE_DESC,
'table': INCLUDE_DESC | INCLUDE_NAME,
'link': INCLUDE_DESC,
'listitem': INCLUDE_DESC,
'outline': INCLUDE_DESC,
'outlineitem': INCLUDE_DESC,
'pagetab': INCLUDE_DESC,
'graphic': INCLUDE_DESC,
- 'pushbutton': INCLUDE_DESC | INCLUDE_NAME,
+ 'pushbutton': INCLUDE_DESC,
'checkbutton': INCLUDE_DESC,
'radiobutton': INCLUDE_DESC,
'combobox': INCLUDE_DESC,
'droplist': INCLUDE_DESC,
'progressbar': INCLUDE_DESC,
'slider': INCLUDE_DESC,
'spinbutton': INCLUDE_DESC,
'diagram': INCLUDE_DESC,
--- a/accessible/src/jsat/VirtualCursorController.jsm
+++ b/accessible/src/jsat/VirtualCursorController.jsm
@@ -9,19 +9,16 @@ const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
var EXPORTED_SYMBOLS = ['VirtualCursorController'];
Cu.import('resource://gre/modules/accessibility/Utils.jsm');
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
-var gAccRetrieval = Cc['@mozilla.org/accessibleRetrieval;1'].
- getService(Ci.nsIAccessibleRetrieval);
-
function BaseTraversalRule(aRoles, aMatchFunc) {
this._matchRoles = aRoles;
this._matchFunc = aMatchFunc;
}
BaseTraversalRule.prototype = {
getMatchRoles: function BaseTraversalRule_getmatchRoles(aRules) {
aRules.value = this._matchRoles;
@@ -344,48 +341,48 @@ var VirtualCursorController = {
if (aLast) {
virtualCursor.moveLast(TraversalRules.Simple);
} else {
try {
virtualCursor.moveNext(aRule || TraversalRules.Simple);
} catch (x) {
this.moveCursorToObject(
virtualCursor,
- gAccRetrieval.getAccessibleFor(aDocument.activeElement), aRule);
+ Utils.AccRetrieval.getAccessibleFor(aDocument.activeElement), aRule);
}
}
},
moveBackward: function moveBackward(aDocument, aFirst, aRule) {
let virtualCursor = Utils.getVirtualCursor(aDocument);
if (aFirst) {
virtualCursor.moveFirst(TraversalRules.Simple);
} else {
try {
virtualCursor.movePrevious(aRule || TraversalRules.Simple);
} catch (x) {
this.moveCursorToObject(
virtualCursor,
- gAccRetrieval.getAccessibleFor(aDocument.activeElement), aRule);
+ Utils.AccRetrieval.getAccessibleFor(aDocument.activeElement), aRule);
}
}
},
activateCurrent: function activateCurrent(document) {
let virtualCursor = Utils.getVirtualCursor(document);
let acc = virtualCursor.position;
if (acc.actionCount > 0) {
acc.doAction(0);
} else {
// XXX Some mobile widget sets do not expose actions properly
// (via ARIA roles, etc.), so we need to generate a click.
// Could possibly be made simpler in the future. Maybe core
// engine could expose nsCoreUtiles::DispatchMouseEvent()?
- let docAcc = gAccRetrieval.getAccessibleFor(this.chromeWin.document);
+ let docAcc = Utils.AccRetrieval.getAccessibleFor(this.chromeWin.document);
let docX = {}, docY = {}, docW = {}, docH = {};
docAcc.getBounds(docX, docY, docW, docH);
let objX = {}, objY = {}, objW = {}, objH = {};
acc.getBounds(objX, objY, objW, objH);
let x = Math.round((objX.value - docX.value) + objW.value / 2);
let y = Math.round((objY.value - docY.value) + objH.value / 2);
--- a/accessible/src/mac/mozAccessible.mm
+++ b/accessible/src/mac/mozAccessible.mm
@@ -96,17 +96,18 @@ GetClosestInterestingAccessible(id anObj
#pragma mark -
- (BOOL)accessibilityIsIgnored
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
// unknown (either unimplemented, or irrelevant) elements are marked as ignored
// as well as expired elements.
- return !mGeckoAccessible || [[self role] isEqualToString:NSAccessibilityUnknownRole];
+ return !mGeckoAccessible || ([[self role] isEqualToString:NSAccessibilityUnknownRole] &&
+ !(mGeckoAccessible->InteractiveState() & states::FOCUSABLE));
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
}
- (NSArray*)accessibilityAttributeNames
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
--- a/accessible/src/mac/mozActionElements.mm
+++ b/accessible/src/mac/mozActionElements.mm
@@ -333,16 +333,29 @@ enum CheckboxValue {
[mTabs release];
mTabs = nil;
}
@end
@implementation mozPaneAccessible
+- (NSUInteger)accessibilityArrayAttributeCount:(NSString*)attribute
+{
+ if (!mGeckoAccessible)
+ return 0;
+
+ // By default this calls -[[mozAccessible children] count].
+ // Since we don't cache mChildren. This is faster.
+ if ([attribute isEqualToString:NSAccessibilityChildrenAttribute])
+ return mGeckoAccessible->ChildCount() ? 1 : 0;
+
+ return [super accessibilityArrayAttributeCount:attribute];
+}
+
- (NSArray*)children
{
if (!mGeckoAccessible)
return nil;
nsDeckFrame* deckFrame = do_QueryFrame(mGeckoAccessible->GetFrame());
nsIFrame* selectedFrame = deckFrame ? deckFrame->GetSelectedBox() : nullptr;
--- a/allmakefiles.sh
+++ b/allmakefiles.sh
@@ -102,22 +102,16 @@ if [ "$OS_ARCH" != "WINNT" -a "$OS_ARCH"
fi
if [ "$USE_ELF_HACK" ]; then
add_makefiles "
build/unix/elfhack/Makefile
"
fi
fi
-if [ "$COMPILER_DEPEND" = "" -a "$MOZ_NATIVE_MAKEDEPEND" = "" ]; then
- add_makefiles "
- config/mkdepend/Makefile
- "
-fi
-
if [ "$ENABLE_MARIONETTE" ]; then
add_makefiles "
testing/marionette/Makefile
testing/marionette/components/Makefile
"
fi
if [ "$ENABLE_TESTS" ]; then
--- a/b2g/app/Makefile.in
+++ b/b2g/app/Makefile.in
@@ -6,16 +6,20 @@ DEPTH = @DEPTH@
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
PREF_JS_EXPORTS = $(srcdir)/b2g.js
+ifdef ENABLE_MARIONETTE
+DEFINES += -DENABLE_MARIONETTE=1
+endif
+
ifndef LIBXUL_SDK
PROGRAM=$(MOZ_APP_NAME)$(BIN_SUFFIX)
CPPSRCS = nsBrowserApp.cpp
LOCAL_INCLUDES += -I$(topsrcdir)/toolkit/xre
LOCAL_INCLUDES += -I$(topsrcdir)/xpcom/base
LOCAL_INCLUDES += -I$(topsrcdir)/xpcom/build
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -427,26 +427,29 @@ pref("full-screen-api.enabled", true);
// fill the whole screen, we'll just make the content fill the client window,
// i.e. it won't give the impression to content that the number of device
// screen pixels changes!
pref("full-screen-api.ignore-widgets", true);
#endif
pref("media.volume.steps", 10);
+#ifdef ENABLE_MARIONETTE
//Enable/disable marionette server, set listening port
pref("marionette.defaultPrefs.enabled", true);
pref("marionette.defaultPrefs.port", 2828);
+#endif
#ifdef MOZ_UPDATER
pref("app.update.enabled", true);
pref("app.update.auto", true);
pref("app.update.silent", true);
pref("app.update.mode", 0);
pref("app.update.incompatible.mode", 0);
+pref("app.update.stage.enabled", true);
pref("app.update.service.enabled", true);
// The URL hosting the update manifest.
pref("app.update.url", "http://update.boot2gecko.org/m2.5/updates.xml");
// Interval at which update manifest is fetched. In units of seconds.
pref("app.update.interval", 3600); // 1 hour
// First interval to elapse before checking for update. In units of
// milliseconds. Capped at 10 seconds.
@@ -508,16 +511,21 @@ pref("dom.ipc.processPriorityManager.ena
pref("dom.ipc.processPriorityManager.gracePeriodMS", 1000);
pref("hal.processPriorityManager.gonk.masterOomAdjust", 0);
pref("hal.processPriorityManager.gonk.foregroundOomAdjust", 1);
pref("hal.processPriorityManager.gonk.backgroundOomAdjust", 2);
pref("hal.processPriorityManager.gonk.masterNice", -1);
pref("hal.processPriorityManager.gonk.foregroundNice", 0);
pref("hal.processPriorityManager.gonk.backgroundNice", 10);
+#ifndef DEBUG
// Enable pre-launching content processes for improved startup time
// (hiding latency).
pref("dom.ipc.processPrelauch.enabled", true);
// Wait this long before pre-launching a new subprocess.
pref("dom.ipc.processPrelauch.delayMs", 1000);
+#endif
// Ignore the "dialog=1" feature in window.open.
pref("dom.disable_window_open_dialog_feature", true);
+
+// Screen reader support
+pref("accessibility.accessfu.activate", 2);
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -13,16 +13,17 @@ Cu.import('resource://gre/modules/XPCOMU
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/ContactService.jsm');
Cu.import('resource://gre/modules/SettingsChangeNotifier.jsm');
Cu.import('resource://gre/modules/Webapps.jsm');
Cu.import('resource://gre/modules/AlarmService.jsm');
Cu.import('resource://gre/modules/ActivitiesService.jsm');
Cu.import('resource://gre/modules/PermissionPromptHelper.jsm');
Cu.import('resource://gre/modules/ObjectWrapper.jsm');
+Cu.import("resource://gre/modules/accessibility/AccessFu.jsm");
XPCOMUtils.defineLazyServiceGetter(Services, 'env',
'@mozilla.org/process/environment;1',
'nsIEnvironment');
XPCOMUtils.defineLazyServiceGetter(Services, 'ss',
'@mozilla.org/content/style-sheet-service;1',
'nsIStyleSheetService');
@@ -43,17 +44,17 @@ XPCOMUtils.defineLazyServiceGetter(Servi
XPCOMUtils.defineLazyGetter(this, 'DebuggerServer', function() {
Cu.import('resource://gre/modules/devtools/dbg-server.jsm');
return DebuggerServer;
});
XPCOMUtils.defineLazyGetter(this, "ppmm", function() {
return Cc["@mozilla.org/parentprocessmessagemanager;1"]
- .getService(Ci.nsIFrameMessageManager);
+ .getService(Ci.nsIMessageListenerManager);
});
function getContentWindow() {
return shell.contentBrowser.contentWindow;
}
var shell = {
@@ -144,16 +145,17 @@ var shell = {
try {
Services.audioManager.masterVolume = 0.5;
} catch(e) {
dump('Error setting master volume: ' + e + '\n');
}
CustomEventManager.init();
WebappsHelper.init();
+ AccessFu.attach(window);
// XXX could factor out into a settings->pref map. Not worth it yet.
SettingsListener.observe("debug.fps.enabled", false, function(value) {
Services.prefs.setBoolPref("layers.acceleration.draw-fps", value);
});
SettingsListener.observe("debug.paint-flashing.enabled", false, function(value) {
Services.prefs.setBoolPref("nglayout.debug.paint_flashing", value);
});
--- a/b2g/components/ContentHandler.js
+++ b/b2g/components/ContentHandler.js
@@ -9,17 +9,18 @@ const Cr = Components.results;
const Cu = Components.utils;
const PDF_CONTENT_TYPE = "application/pdf";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyGetter(this, "cpmm", function() {
- return Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager);
+ return Cc["@mozilla.org/childprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageSender);
});
function log(aMsg) {
let msg = "ContentHandler.js: " + (aMsg.join ? aMsg.join("") : aMsg);
Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService)
.logStringMessage(msg);
dump(msg + "\n");
}
--- a/b2g/components/DirectoryProvider.js
+++ b/b2g/components/DirectoryProvider.js
@@ -17,17 +17,18 @@ function DirectoryProvider() {
DirectoryProvider.prototype = {
classID: Components.ID("{9181eb7c-6f87-11e1-90b1-4f59d80dd2e5}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider]),
getFile: function dp_getFile(prop, persistent) {
#ifdef MOZ_WIDGET_GONK
- let localProps = ["cachePDir", "webappsDir", "PrefD", "indexedDBPDir", "permissionDBPDir"];
+ let localProps = ["cachePDir", "webappsDir", "PrefD", "indexedDBPDir",
+ "permissionDBPDir", "UpdRootD"];
if (localProps.indexOf(prop) != -1) {
prop.persistent = true;
let file = Cc["@mozilla.org/file/local;1"]
.createInstance(Ci.nsILocalFile)
file.initWithPath(LOCAL_DIR);
return file;
}
#endif
--- a/b2g/components/MozKeyboard.js
+++ b/b2g/components/MozKeyboard.js
@@ -9,17 +9,17 @@ const Ci = Components.interfaces;
const Cu = Components.utils;
const kFormsFrameScript = "chrome://browser/content/forms.js";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/ObjectWrapper.jsm");
const messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
- .getService(Ci.nsIChromeFrameMessageManager);
+ .getService(Ci.nsIMessageBroadcaster);
// -----------------------------------------------------------------------
// MozKeyboard
// -----------------------------------------------------------------------
function MozKeyboard() { }
@@ -65,29 +65,29 @@ MozKeyboard.prototype = {
sendKey: function mozKeyboardSendKey(keyCode, charCode) {
charCode = (charCode == undefined) ? keyCode : charCode;
["keydown", "keypress", "keyup"].forEach((function sendKey(type) {
this._utils.sendKeyEvent(type, keyCode, charCode, null);
}).bind(this));
},
setSelectedOption: function mozKeyboardSetSelectedOption(index) {
- messageManager.sendAsyncMessage("Forms:Select:Choice", {
+ messageManager.broadcastAsyncMessage("Forms:Select:Choice", {
"index": index
});
},
setValue: function mozKeyboardSetValue(value) {
- messageManager.sendAsyncMessage("Forms:Input:Value", {
+ messageManager.broadcastAsyncMessage("Forms:Input:Value", {
"value": value
});
},
setSelectedOptions: function mozKeyboardSetSelectedOptions(indexes) {
- messageManager.sendAsyncMessage("Forms:Select:Choice", {
+ messageManager.broadcastAsyncMessage("Forms:Select:Choice", {
"indexes": indexes || []
});
},
set onfocuschange(val) {
this._focusHandler = val;
},
--- a/b2g/components/UpdatePrompt.js
+++ b/b2g/components/UpdatePrompt.js
@@ -74,16 +74,16 @@ UpdatePrompt.prototype = {
this._selfDestructTimer = timer;
#endif
},
showUpdateInstalled: function UP_showUpdateInstalled() { },
showUpdateError: function UP_showUpdateError(aUpdate) {
if (aUpdate.state == "failed") {
- log("Failed to download update");
+ log("Failed to download update, errorCode: " + aUpdate.errorCode);
}
},
showUpdateHistory: function UP_showUpdateHistory(aParent) { },
};
const NSGetFactory = XPCOMUtils.generateNSGetFactory([UpdatePrompt]);
--- a/b2g/config/mozconfigs/gb_armv7a_gecko/debug
+++ b/b2g/config/mozconfigs/gb_armv7a_gecko/debug
@@ -6,12 +6,12 @@ ac_add_options --enable-application=b2g
ac_add_options --target=arm-android-eabi
ac_add_options --with-gonk="$topsrcdir/gonk-toolchain"
ac_add_options --with-gonk-toolchain-prefix="$topsrcdir/gonk-toolchain/prebuilt/$TOOLCHAIN_HOST/toolchain/arm-eabi-4.4.3/bin/arm-eabi-"
ac_add_options --disable-elf-hack
ac_add_options --enable-debug-symbols
ac_add_options --enable-debug
ac_add_options --with-ccache
-ac_add_options --enable-marionette
+ENABLE_MARIONETTE=1
# Enable dump() from JS.
export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
--- a/b2g/config/mozconfigs/gb_armv7a_gecko/nightly
+++ b/b2g/config/mozconfigs/gb_armv7a_gecko/nightly
@@ -6,12 +6,12 @@ ac_add_options --enable-application=b2g
ac_add_options --target=arm-android-eabi
ac_add_options --with-gonk="$topsrcdir/gonk-toolchain"
ac_add_options --with-gonk-toolchain-prefix="$topsrcdir/gonk-toolchain/prebuilt/$TOOLCHAIN_HOST/toolchain/arm-eabi-4.4.3/bin/arm-eabi-"
ac_add_options --disable-elf-hack
ac_add_options --enable-debug-symbols
ac_add_options --enable-profiling
ac_add_options --with-ccache
-ac_add_options --enable-marionette
+ENABLE_MARIONETTE=1
# Enable dump() from JS.
export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
--- a/b2g/config/mozconfigs/ics_armv7a_gecko/debug
+++ b/b2g/config/mozconfigs/ics_armv7a_gecko/debug
@@ -9,12 +9,12 @@ ac_add_options --target=arm-android-eabi
ac_add_options --with-gonk="$topsrcdir/gonk-toolchain"
export TOOLCHAIN_HOST=linux-x86
export GONK_PRODUCT=generic
ac_add_options --with-gonk-toolchain-prefix="$topsrcdir/gonk-toolchain/prebuilt/$TOOLCHAIN_HOST/toolchain/arm-linux-androideabi-4.4.x/bin/arm-linux-androideabi-"
ac_add_options --disable-elf-hack
ac_add_options --enable-debug-symbols
ac_add_options --enable-debug
#ac_add_options --with-ccache
-ac_add_options --enable-marionette
+ENABLE_MARIONETTE=1
# Enable dump() from JS.
export CXXFLAGS="-DMOZ_ENABLE_JS_DUMP -include $topsrcdir/gonk-toolchain/gonk-misc/Unicode.h -include $topsrcdir/gonk-toolchain/system/vold/ResponseCode.h"
--- a/b2g/config/mozconfigs/ics_armv7a_gecko/nightly
+++ b/b2g/config/mozconfigs/ics_armv7a_gecko/nightly
@@ -9,12 +9,12 @@ ac_add_options --target=arm-android-eabi
ac_add_options --with-gonk="$topsrcdir/gonk-toolchain"
export TOOLCHAIN_HOST=linux-x86
export GONK_PRODUCT=generic
ac_add_options --with-gonk-toolchain-prefix="$topsrcdir/gonk-toolchain/prebuilt/$TOOLCHAIN_HOST/toolchain/arm-linux-androideabi-4.4.x/bin/arm-linux-androideabi-"
ac_add_options --disable-elf-hack
ac_add_options --enable-debug-symbols
ac_add_options --enable-profiling
#ac_add_options --with-ccache
-ac_add_options --enable-marionette
+ENABLE_MARIONETTE=1
# Enable dump() from JS.
export CXXFLAGS="-DMOZ_ENABLE_JS_DUMP -include $topsrcdir/gonk-toolchain/gonk-misc/Unicode.h -include $topsrcdir/gonk-toolchain/system/vold/ResponseCode.h"
--- a/b2g/config/mozconfigs/linux32_gecko/nightly
+++ b/b2g/config/mozconfigs/linux32_gecko/nightly
@@ -28,11 +28,11 @@ export MOZ_TELEMETRY_REPORTING=1
# Enable parallel compiling
mk_add_options MOZ_MAKE_FLAGS="-j4"
# Use ccache
ac_add_options --with-ccache=/usr/bin/ccache
#B2G options
ac_add_options --enable-application=b2g
-ac_add_options --enable-marionette
+ENABLE_MARIONETTE=1
ac_add_options --disable-elf-hack
export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
--- a/b2g/config/mozconfigs/linux64_gecko/nightly
+++ b/b2g/config/mozconfigs/linux64_gecko/nightly
@@ -30,11 +30,11 @@ export MOZ_TELEMETRY_REPORTING=1
# Enable parallel compiling
mk_add_options MOZ_MAKE_FLAGS="-j4"
# Use ccache
ac_add_options --with-ccache=/usr/bin/ccache
#B2G options
ac_add_options --enable-application=b2g
-ac_add_options --enable-marionette
+ENABLE_MARIONETTE=1
ac_add_options --disable-elf-hack
export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
--- a/b2g/config/mozconfigs/macosx64_gecko/nightly
+++ b/b2g/config/mozconfigs/macosx64_gecko/nightly
@@ -18,11 +18,11 @@ mk_add_options MOZ_MAKE_FLAGS="-j12"
# Treat warnings as errors in directories with FAIL_ON_WARNINGS.
ac_add_options --enable-warnings-as-errors
# B2G Stuff
ac_add_options --enable-application=b2g
ac_add_options --enable-debug-symbols
ac_add_options --with-ccache
-ac_add_options --enable-marionette
+ENABLE_MARIONETTE=1
export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
--- a/b2g/config/mozconfigs/win32_gecko/nightly
+++ b/b2g/config/mozconfigs/win32_gecko/nightly
@@ -19,11 +19,11 @@ mk_add_options MOZ_MAKE_FLAGS=-j1
if test "$PROCESSOR_ARCHITECTURE" = "AMD64" -o "$PROCESSOR_ARCHITEW6432" = "AMD64"; then
. $topsrcdir/build/win32/mozconfig.vs2010-win64
else
. $topsrcdir/build/win32/mozconfig.vs2010
fi
# B2G Options
ac_add_options --enable-application=b2g
-ac_add_options --enable-marionette
+ENABLE_MARIONETTE=1
export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
--- a/b2g/confvars.sh
+++ b/b2g/confvars.sh
@@ -1,18 +1,16 @@
# 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/.
MOZ_APP_BASENAME=B2G
MOZ_APP_VENDOR=Mozilla
-MOZ_APP_VERSION=17.0a1
-
-MOZ_UA_OS_AGNOSTIC=1
+MOZ_APP_VERSION=18.0a1
MOZ_APP_UA_NAME=Firefox
MOZ_BRANDING_DIRECTORY=b2g/branding/unofficial
MOZ_OFFICIAL_BRANDING_DIRECTORY=b2g/branding/official
# MOZ_APP_DISPLAYNAME is set by branding/configure.sh
MOZ_SAFE_BROWSING=
MOZ_SERVICES_SYNC=1
@@ -33,11 +31,11 @@ if test "$LIBXUL_SDK"; then
MOZ_XULRUNNER=1
else
MOZ_XULRUNNER=
MOZ_PLACES=1
fi
MOZ_APP_ID={3c2e2abc-06d4-11e1-ac3b-374f68613e61}
MOZ_EXTENSION_MANAGER=1
-ENABLE_MARIONETTE=1
MOZ_SYS_MSG=1
+MOZ_TOOLKIT_SEARCH=
--- a/b2g/installer/Makefile.in
+++ b/b2g/installer/Makefile.in
@@ -33,16 +33,20 @@ JAREXT=.jar
else
JAREXT=
endif
DEFINES += -DJAREXT=$(JAREXT)
include $(topsrcdir)/ipc/app/defs.mk
DEFINES += -DMOZ_CHILD_PROCESS_NAME=$(MOZ_CHILD_PROCESS_NAME)
+ifdef ENABLE_MARIONETTE
+DEFINES += -DENABLE_MARIONETTE=1
+endif
+
ifdef MOZ_PKG_MANIFEST_P
MOZ_PKG_MANIFEST = package-manifest
endif
MOZ_POST_STAGING_CMD = find chrome -type f -name *.properties -exec sed -i '/^\#/d' {} \;
include $(topsrcdir)/toolkit/mozapps/installer/packager.mk
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -339,18 +339,16 @@
@BINPATH@/components/nsBrowserContentHandler.js
@BINPATH@/components/nsBrowserGlue.js
@BINPATH@/components/nsSetDefaultBrowser.manifest
@BINPATH@/components/nsSetDefaultBrowser.js
@BINPATH@/components/BrowserPlaces.manifest
@BINPATH@/components/nsPrivateBrowsingService.manifest
@BINPATH@/components/nsPrivateBrowsingService.js
@BINPATH@/components/toolkitsearch.manifest
-@BINPATH@/components/nsSearchService.js
-@BINPATH@/components/nsSearchSuggestions.js
@BINPATH@/components/nsTryToClose.manifest
@BINPATH@/components/nsTryToClose.js
@BINPATH@/components/passwordmgr.manifest
@BINPATH@/components/nsLoginInfo.js
@BINPATH@/components/nsLoginManager.js
@BINPATH@/components/nsLoginManagerPrompter.js
@BINPATH@/components/storage-Legacy.js
@BINPATH@/components/storage-mozStorage.js
@@ -670,20 +668,22 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DL
[b2g]
@BINPATH@/chrome/icons/
@BINPATH@/chrome/chrome@JAREXT@
@BINPATH@/chrome/chrome.manifest
@BINPATH@/components/B2GComponents.manifest
@BINPATH@/components/B2GComponents.xpt
@BINPATH@/components/CameraContent.js
@BINPATH@/@DLL_PREFIX@omxplugin@DLL_SUFFIX@
+#ifdef ENABLE_MARIONETTE
@BINPATH@/chrome/marionette@JAREXT@
@BINPATH@/chrome/marionette.manifest
@BINPATH@/components/MarionetteComponents.manifest
@BINPATH@/components/marionettecomponent.js
+#endif
@BINPATH@/components/AlertsService.js
@BINPATH@/components/ContentPermissionPrompt.js
#ifdef MOZ_UPDATER
@BINPATH@/components/UpdatePrompt.js
#endif
@BINPATH@/components/MozKeyboard.js
@BINPATH@/components/DirectoryProvider.js
@BINPATH@/components/ActivitiesGlue.js
--- a/browser/app/Makefile.in
+++ b/browser/app/Makefile.in
@@ -7,16 +7,20 @@ topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
DIRS = profile/extensions
dist_dest = $(DIST)/$(MOZ_MACBUNDLE_NAME)
+ifdef ENABLE_MARIONETTE
+DEFINES += -DENABLE_MARIONETTE=1
+endif
+
PREF_JS_EXPORTS = $(srcdir)/profile/firefox.js \
$(NULL)
# hardcode en-US for the moment
AB_CD = en-US
DEFINES += \
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -1,10 +1,10 @@
<?xml version="1.0"?>
-<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1345147390000">
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1345657032000">
<emItems>
<emItem blockID="i58" id="webmaster@buzzzzvideos.info">
<versionRange minVersion="0" maxVersion="*">
</versionRange>
</emItem>
<emItem blockID="i41" id="{99079a25-328f-4bd4-be04-00955acaa0a7}">
<versionRange minVersion="0.1" maxVersion="4.3.1.00" severity="1">
</versionRange>
@@ -434,16 +434,19 @@
<match name="filename" exp="npmozax\.dll" /> <versionRange minVersion="0" maxVersion="*"></versionRange>
</pluginItem>
<pluginItem blockID="p113">
<match name="filename" exp="npuplaypc\.dll" /> <versionRange minVersion="0" maxVersion="1.0.0.0" severity="1"></versionRange>
</pluginItem>
<pluginItem blockID="p123">
<match name="filename" exp="JavaPlugin2_NPAPI\.plugin" /> <versionRange minVersion="0" maxVersion="14.2.0" severity="1"></versionRange>
</pluginItem>
+ <pluginItem blockID="p129">
+ <match name="filename" exp="Silverlight\.plugin" /> <versionRange minVersion="0" maxVersion="5.0.99999" severity="1"></versionRange>
+ </pluginItem>
</pluginItems>
<gfxItems>
<gfxBlacklistEntry blockID="g35"> <os>WINNT 6.1</os> <vendor>0x10de</vendor> <devices>
<device>0x0a6c</device>
</devices>
<feature>DIRECT2D</feature> <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus> <driverVersion>8.17.12.5896</driverVersion> <driverVersionComparator>LESS_THAN_OR_EQUAL</driverVersionComparator> </gfxBlacklistEntry>
<gfxBlacklistEntry blockID="g36"> <os>WINNT 6.1</os> <vendor>0x10de</vendor> <devices>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1002,18 +1002,16 @@ pref("devtools.gcli.allowSet", false);
pref("devtools.commands.dir", "");
// Enable the Inspector
pref("devtools.inspector.enabled", true);
pref("devtools.inspector.htmlHeight", 112);
pref("devtools.inspector.htmlPanelOpen", false);
pref("devtools.inspector.sidebarOpen", false);
pref("devtools.inspector.activeSidebar", "ruleview");
-pref("devtools.inspector.highlighterShowVeil", true);
-pref("devtools.inspector.highlighterShowInfobar", true);
// Enable the Layout View
pref("devtools.layoutview.enabled", true);
pref("devtools.layoutview.open", false);
// Enable the Responsive UI tool
pref("devtools.responsiveUI.enabled", true);
@@ -1025,17 +1023,19 @@ pref("devtools.debugger.remote-autoconne
pref("devtools.debugger.remote-connection-retries", 3);
pref("devtools.debugger.remote-timeout", 3000);
// The default Debugger UI settings
pref("devtools.debugger.ui.height", 250);
pref("devtools.debugger.ui.remote-win.width", 900);
pref("devtools.debugger.ui.remote-win.height", 400);
pref("devtools.debugger.ui.stackframes-width", 200);
+pref("devtools.debugger.ui.stackframes-pane-visible", true);
pref("devtools.debugger.ui.variables-width", 300);
+pref("devtools.debugger.ui.variables-pane-visible", true);
// Enable the style inspector
pref("devtools.styleinspector.enabled", true);
// Enable the Tilt inspector
pref("devtools.tilt.enabled", true);
pref("devtools.tilt.intro_transition", true);
pref("devtools.tilt.outro_transition", true);
@@ -1085,16 +1085,19 @@ pref("devtools.webconsole.filter.csserro
pref("devtools.webconsole.filter.cssparser", true);
pref("devtools.webconsole.filter.exception", true);
pref("devtools.webconsole.filter.jswarn", true);
pref("devtools.webconsole.filter.error", true);
pref("devtools.webconsole.filter.warn", true);
pref("devtools.webconsole.filter.info", true);
pref("devtools.webconsole.filter.log", true);
+// Text size in the Web Console. Use 0 for the system default size.
+pref("devtools.webconsole.fontSize", 0);
+
// The number of lines that are displayed in the web console for the Net,
// CSS, JS and Web Developer categories.
pref("devtools.hud.loglimit.network", 200);
pref("devtools.hud.loglimit.cssparser", 200);
pref("devtools.hud.loglimit.exception", 200);
pref("devtools.hud.loglimit.console", 200);
// The developer tools editor configuration:
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -145,66 +145,86 @@ var gPluginHandler = {
self.addLinkClickCallback(updateLink, "openPluginUpdatePage");
/* FALLTHRU */
case "PluginVulnerableNoUpdate":
case "PluginClickToPlay":
self._handleClickToPlayEvent(plugin);
break;
+ case "PluginPlayPreview":
+ self._handlePlayPreviewEvent(plugin);
+ break;
+
case "PluginDisabled":
let manageLink = doc.getAnonymousElementByAttribute(plugin, "class", "managePluginsLink");
self.addLinkClickCallback(manageLink, "managePlugins");
break;
}
// Hide the in-content UI if it's too big. The crashed plugin handler already did this.
if (event.type != "PluginCrashed") {
let overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
/* overlay might be null, so only operate on it if it exists */
if (overlay != null && self.isTooSmall(plugin, overlay))
overlay.style.visibility = "hidden";
}
},
+ canActivatePlugin: function PH_canActivatePlugin(objLoadingContent) {
+ return !objLoadingContent.activated &&
+ objLoadingContent.pluginFallbackType !== Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW;
+ },
+
activatePlugins: function PH_activatePlugins(aContentWindow) {
let browser = gBrowser.getBrowserForDocument(aContentWindow.document);
browser._clickToPlayPluginsActivated = true;
let cwu = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
let plugins = cwu.plugins;
for (let plugin of plugins) {
let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
- if (!objLoadingContent.activated)
+ if (gPluginHandler.canActivatePlugin(objLoadingContent))
objLoadingContent.playPlugin();
}
let notification = PopupNotifications.getNotification("click-to-play-plugins", browser);
if (notification)
notification.remove();
},
activateSinglePlugin: function PH_activateSinglePlugin(aContentWindow, aPlugin) {
let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
- if (!objLoadingContent.activated)
+ if (gPluginHandler.canActivatePlugin(objLoadingContent))
objLoadingContent.playPlugin();
let cwu = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
let haveUnplayedPlugins = cwu.plugins.some(function(plugin) {
let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
- return (plugin != aPlugin && !objLoadingContent.activated);
+ return (plugin != aPlugin && gPluginHandler.canActivatePlugin(objLoadingContent));
});
let browser = gBrowser.getBrowserForDocument(aContentWindow.document);
let notification = PopupNotifications.getNotification("click-to-play-plugins", browser);
if (notification && !haveUnplayedPlugins) {
browser._clickToPlayDoorhangerShown = false;
notification.remove();
}
},
+ stopPlayPreview: function PH_stopPlayPreview(aPlugin, aPlayPlugin) {
+ let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ if (objLoadingContent.activated)
+ return;
+
+ if (aPlayPlugin)
+ objLoadingContent.playPlugin();
+ else
+ objLoadingContent.cancelPlayPreview();
+ },
+
newPluginInstalled : function(event) {
// browser elements are anonymous so we can't just use target.
var browser = event.originalTarget;
// clear the plugin list, now that at least one plugin has been installed
browser.missingPlugins = null;
var notificationBox = gBrowser.getNotificationBox(browser);
var notification = notificationBox.getNotificationWithValue("missing-plugins");
@@ -285,32 +305,72 @@ var gPluginHandler = {
gPluginHandler.activateSinglePlugin(aEvent.target.ownerDocument.defaultView.top, aPlugin);
}, true);
}
if (!browser._clickToPlayDoorhangerShown)
gPluginHandler._showClickToPlayNotification(browser);
},
+ _handlePlayPreviewEvent: function PH_handlePlayPreviewEvent(aPlugin) {
+ let doc = aPlugin.ownerDocument;
+ let previewContent = doc.getAnonymousElementByAttribute(aPlugin, "class", "previewPluginContent");
+ if (!previewContent) {
+ // the XBL binding is not attached (element is display:none), fallback to click-to-play logic
+ gPluginHandler.stopPlayPreview(aPlugin, false);
+ return;
+ }
+ let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
+ if (!iframe) {
+ // lazy initialization of the iframe
+ iframe = doc.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
+ iframe.className = "previewPluginContentFrame";
+ previewContent.appendChild(iframe);
+
+ // Force a style flush, so that we ensure our binding is attached.
+ aPlugin.clientTop;
+ }
+ let pluginInfo = getPluginInfo(aPlugin);
+ let playPreviewUri = "data:application/x-moz-playpreview;," + pluginInfo.mimetype;
+ iframe.src = playPreviewUri;
+
+ // MozPlayPlugin event can be dispatched from the extension chrome
+ // code to replace the preview content with the native plugin
+ previewContent.addEventListener("MozPlayPlugin", function playPluginHandler(aEvent) {
+ if (!aEvent.isTrusted)
+ return;
+
+ previewContent.removeEventListener("MozPlayPlugin", playPluginHandler, true);
+
+ let playPlugin = !aEvent.detail;
+ gPluginHandler.stopPlayPreview(aPlugin, playPlugin);
+
+ // cleaning up: removes overlay iframe from the DOM
+ let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
+ if (iframe)
+ previewContent.removeChild(iframe);
+ }, true);
+ },
+
reshowClickToPlayNotification: function PH_reshowClickToPlayNotification() {
if (!Services.prefs.getBoolPref("plugins.click_to_play"))
return;
let browser = gBrowser.selectedBrowser;
let pluginsPermission = Services.perms.testPermission(browser.currentURI, "plugins");
if (pluginsPermission == Ci.nsIPermissionManager.DENY_ACTION)
return;
let contentWindow = browser.contentWindow;
let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
let pluginNeedsActivation = cwu.plugins.some(function(plugin) {
let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
- return !objLoadingContent.activated;
+ return gPluginHandler.canActivatePlugin(objLoadingContent);
});
if (pluginNeedsActivation)
gPluginHandler._showClickToPlayNotification(browser);
},
_showClickToPlayNotification: function PH_showClickToPlayNotification(aBrowser) {
aBrowser._clickToPlayDoorhangerShown = true;
let contentWindow = aBrowser.contentWindow;
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -36,16 +36,17 @@ let SocialUI = {
// Exceptions here sometimes don't get reported properly, report them
// manually :(
try {
this.updateToggleCommand();
SocialShareButton.updateButtonHiddenState();
SocialToolbar.updateButtonHiddenState();
SocialSidebar.updateSidebar();
SocialChatBar.update();
+ SocialFlyout.unload();
} catch (e) {
Components.utils.reportError(e);
throw e;
}
break;
case "social:ambient-notification-changed":
SocialToolbar.updateButton();
break;
@@ -66,19 +67,21 @@ let SocialUI = {
_providerReady: function SocialUI_providerReady() {
// If we couldn't find a provider, nothing to do here.
if (!Social.provider)
return;
this.updateToggleCommand();
let toggleCommand = this.toggleCommand;
- let label = gNavigatorBundle.getFormattedString("social.enable.label",
- [Social.provider.name]);
- let accesskey = gNavigatorBundle.getString("social.enable.accesskey");
+ let brandShortName = document.getElementById("bundle_brand").getString("brandShortName");
+ let label = gNavigatorBundle.getFormattedString("social.toggle.label",
+ [Social.provider.name,
+ brandShortName]);
+ let accesskey = gNavigatorBundle.getString("social.toggle.accesskey");
toggleCommand.setAttribute("label", label);
toggleCommand.setAttribute("accesskey", accesskey);
SocialToolbar.init();
SocialShareButton.init();
SocialSidebar.init();
},
@@ -132,17 +135,17 @@ let SocialUI = {
Social.lastEventReceived = now;
// Enable the social functionality, and indicate that it was activated
Social.active = true;
// Show a warning, allow undoing the activation
let description = document.getElementById("social-activation-message");
let brandShortName = document.getElementById("bundle_brand").getString("brandShortName");
- let message = gNavigatorBundle.getFormattedString("social.activated.message",
+ let message = gNavigatorBundle.getFormattedString("social.activated.description",
[Social.provider.name, brandShortName]);
description.value = message;
SocialUI.notificationPanel.hidden = false;
setTimeout(function () {
SocialUI.notificationPanel.openPopup(SocialToolbar.button, "bottomcenter topright");
}.bind(this), 0);
@@ -164,27 +167,143 @@ let SocialChatBar = {
},
// Whether the chats can be shown for this window.
get canShow() {
let docElem = document.documentElement;
let chromeless = docElem.getAttribute("disablechrome") ||
docElem.getAttribute("chromehidden").indexOf("extrachrome") >= 0;
return Social.uiVisible && !chromeless;
},
- newChat: function(aProvider, aURL, aCallback) {
+ openChat: function(aProvider, aURL, aCallback, aMode) {
if (this.canShow)
- this.chatbar.newChat(aProvider, aURL, aCallback);
+ this.chatbar.openChat(aProvider, aURL, aCallback, aMode);
},
update: function() {
if (!this.canShow)
this.chatbar.removeAll();
}
}
+function sizeSocialPanelToContent(iframe) {
+ // FIXME: bug 764787: Maybe we can use nsIDOMWindowUtils.getRootBounds() here?
+ // Need to handle dynamic sizing
+ let doc = iframe.contentDocument;
+ if (!doc) {
+ return;
+ }
+ // "notif" is an implementation detail that we should get rid of
+ // eventually
+ let body = doc.getElementById("notif") || doc.body;
+ if (!body || !body.firstChild) {
+ return;
+ }
+
+ let [height, width] = [body.firstChild.offsetHeight || 300, 330];
+ iframe.style.width = width + "px";
+ iframe.style.height = height + "px";
+}
+
+let SocialFlyout = {
+ get panel() {
+ return document.getElementById("social-flyout-panel");
+ },
+
+ dispatchPanelEvent: function(name) {
+ let doc = this.panel.firstChild.contentDocument;
+ let evt = doc.createEvent("CustomEvent");
+ evt.initCustomEvent(name, true, true, {});
+ doc.documentElement.dispatchEvent(evt);
+ },
+
+ _createFrame: function() {
+ let panel = this.panel;
+ if (!Social.provider || panel.firstChild)
+ return;
+ // create and initialize the panel for this window
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("type", "content");
+ iframe.setAttribute("flex", "1");
+ iframe.setAttribute("origin", Social.provider.origin);
+ panel.appendChild(iframe);
+ },
+
+ unload: function() {
+ let panel = this.panel;
+ if (!panel.firstChild)
+ return
+ panel.removeChild(panel.firstChild);
+ },
+
+ onShown: function(aEvent) {
+ let iframe = this.panel.firstChild;
+ iframe.docShell.isActive = true;
+ iframe.docShell.isAppTab = true;
+ if (iframe.contentDocument.readyState == "complete") {
+ this.dispatchPanelEvent("socialFrameShow");
+ } else {
+ // first time load, wait for load and dispatch after load
+ iframe.addEventListener("load", function panelBrowserOnload(e) {
+ iframe.removeEventListener("load", panelBrowserOnload, true);
+ setTimeout(function() {
+ SocialFlyout.dispatchPanelEvent("socialFrameShow");
+ }, 0);
+ }, true);
+ }
+ },
+
+ onHidden: function(aEvent) {
+ this.panel.firstChild.docShell.isActive = false;
+ this.dispatchPanelEvent("socialFrameHide");
+ },
+
+ open: function(aURL, yOffset, aCallback) {
+ if (!Social.provider)
+ return;
+ let panel = this.panel;
+ if (!panel.firstChild)
+ this._createFrame();
+ panel.hidden = false;
+ let iframe = panel.firstChild;
+
+ let src = iframe.getAttribute("src");
+ if (src != aURL) {
+ iframe.addEventListener("load", function documentLoaded() {
+ iframe.removeEventListener("load", documentLoaded, true);
+ sizeSocialPanelToContent(iframe);
+ if (aCallback) {
+ try {
+ aCallback(iframe.contentWindow);
+ } catch(e) {
+ Cu.reportError(e);
+ }
+ }
+ }, true);
+ iframe.setAttribute("src", aURL);
+ }
+ else if (aCallback) {
+ try {
+ aCallback(iframe.contentWindow);
+ } catch(e) {
+ Cu.reportError(e);
+ }
+ }
+
+ sizeSocialPanelToContent(iframe);
+ let anchor = document.getElementById("social-sidebar-browser");
+ panel.openPopup(anchor, "start_before", 0, yOffset, false, false);
+ }
+}
+
let SocialShareButton = {
+ // promptImages and promptMessages being null means we are yet to get the
+ // message back from the provider with the images and icons (or that we got
+ // the response but determined it was invalid.)
+ promptImages: null,
+ promptMessages: null,
+
// Called once, after window load, when the Social.provider object is initialized
init: function SSB_init() {
this.updateButtonHiddenState();
this.updateProfileInfo();
},
updateProfileInfo: function SSB_updateProfileInfo() {
let profileRow = document.getElementById("editSharePopupHeader");
@@ -193,33 +312,88 @@ let SocialShareButton = {
profileRow.hidden = false;
let portrait = document.getElementById("socialUserPortrait");
portrait.setAttribute("src", profile.portrait || "chrome://browser/skin/social/social.png");
let displayName = document.getElementById("socialUserDisplayName");
displayName.setAttribute("label", profile.displayName);
} else {
profileRow.hidden = true;
}
+ // XXX - this shouldn't be done as part of updateProfileInfo, but instead
+ // whenever we notice the provider has changed - but the concept of
+ // "provider changed" will only exist once bug 774520 lands.
+ this.promptImages = null;
+ this.promptMessages = null;
+ // get the recommend-prompt info.
+ let port = Social.provider._getWorkerPort();
+ if (port) {
+ port.onmessage = function(evt) {
+ if (evt.data.topic == "social.user-recommend-prompt-response") {
+ port.close();
+ this.acceptRecommendInfo(evt.data.data);
+ this.updateButtonHiddenState();
+ this.updateShareState();
+ }
+ }.bind(this);
+ port.postMessage({topic: "social.user-recommend-prompt"});
+ }
+ },
+
+ acceptRecommendInfo: function SSB_acceptRecommendInfo(data) {
+ // Accept *and validate* the user-recommend-prompt-response message.
+ let promptImages = {};
+ let promptMessages = {};
+ function reportError(reason) {
+ Cu.reportError("Invalid recommend data from provider: " + reason + ": sharing is disabled for this provider");
+ return false;
+ }
+ if (!data ||
+ !data.images || typeof data.images != "object" ||
+ !data.messages || typeof data.messages != "object") {
+ return reportError("data is missing valid 'images' or 'messages' elements");
+ }
+ for (let sub of ["share", "unshare"]) {
+ let url = data.images[sub];
+ if (!url || typeof url != "string" || url.length == 0) {
+ return reportError('images["' + sub + '"] is missing or not a non-empty string');
+ }
+ // resolve potentially relative URLs then check the scheme is acceptable.
+ url = Services.io.newURI(Social.provider.origin, null, null).resolve(url);
+ let uri = Services.io.newURI(url, null, null);
+ if (!uri.schemeIs("http") && !uri.schemeIs("https") && !uri.schemeIs("data")) {
+ return reportError('images["' + sub + '"] does not have a valid scheme');
+ }
+ promptImages[sub] = url;
+ }
+ for (let sub of ["shareTooltip", "unshareTooltip", "sharedLabel", "unsharedLabel"]) {
+ if (typeof data.messages[sub] != "string" || data.messages[sub].length == 0) {
+ return reportError('messages["' + sub + '"] is not a valid string');
+ }
+ promptMessages[sub] = data.messages[sub];
+ }
+ this.promptImages = promptImages;
+ this.promptMessages = promptMessages;
+ return true;
},
get shareButton() {
return document.getElementById("share-button");
},
get sharePopup() {
return document.getElementById("editSharePopup");
},
dismissSharePopup: function SSB_dismissSharePopup() {
this.sharePopup.hidePopup();
},
updateButtonHiddenState: function SSB_updateButtonHiddenState() {
let shareButton = this.shareButton;
if (shareButton)
- shareButton.hidden = !Social.uiVisible;
+ shareButton.hidden = !Social.uiVisible || this.promptImages == null;
},
onClick: function SSB_onClick(aEvent) {
if (aEvent.button != 0)
return;
// Don't bubble to the textbox, to avoid unwanted selection of the address.
aEvent.stopPropagation();
@@ -252,49 +426,51 @@ let SocialShareButton = {
},
updateShareState: function SSB_updateShareState() {
let currentPageShared = Social.isPageShared(gBrowser.currentURI);
// Provide a11y-friendly notification of share.
let status = document.getElementById("share-button-status");
if (status) {
+ // XXX - this should also be capable of reflecting that the page was
+ // unshared (ie, it needs to manage three-states: (1) nothing done, (2)
+ // shared, (3) shared then unshared)
+ // Note that we *do* have an appropriate string from the provider for
+ // this (promptMessages['unsharedLabel'] but currently lack a way of
+ // tracking this state)
let statusString = currentPageShared ?
- gNavigatorBundle.getString("social.pageShared.label") : "";
+ this.promptMessages['sharedLabel'] : "";
status.setAttribute("value", statusString);
}
// Update the share button, if present
let shareButton = this.shareButton;
- if (!shareButton)
+ if (!shareButton || shareButton.hidden)
return;
+ let imageURL;
if (currentPageShared) {
shareButton.setAttribute("shared", "true");
- shareButton.setAttribute("tooltiptext", gNavigatorBundle.getString("social.shareButton.sharedtooltip"));
+ shareButton.setAttribute("tooltiptext", this.promptMessages['unshareTooltip']);
+ imageURL = this.promptImages["unshare"]
} else {
shareButton.removeAttribute("shared");
- shareButton.setAttribute("tooltiptext", gNavigatorBundle.getString("social.shareButton.tooltip"));
+ shareButton.setAttribute("tooltiptext", this.promptMessages['shareTooltip']);
+ imageURL = this.promptImages["share"]
}
+ shareButton.style.backgroundImage = 'url("' + encodeURI(imageURL) + '")';
}
};
var SocialToolbar = {
// Called once, after window load, when the Social.provider object is initialized
init: function SocialToolbar_init() {
document.getElementById("social-provider-image").setAttribute("image", Social.provider.iconURL);
- let removeItem = document.getElementById("social-remove-menuitem");
- let brandShortName = document.getElementById("bundle_brand").getString("brandShortName");
- let label = gNavigatorBundle.getFormattedString("social.remove.label",
- [brandShortName]);
- let accesskey = gNavigatorBundle.getString("social.remove.accesskey");
- removeItem.setAttribute("label", label);
- removeItem.setAttribute("accesskey", accesskey);
-
let statusAreaPopup = document.getElementById("social-statusarea-popup");
statusAreaPopup.addEventListener("popupshown", function(e) {
this.button.setAttribute("open", "true");
}.bind(this));
statusAreaPopup.addEventListener("popuphidden", function(e) {
this.button.removeAttribute("open");
}.bind(this));
@@ -339,33 +515,36 @@ var SocialToolbar = {
},
updateButton: function SocialToolbar_updateButton() {
this.updateButtonHiddenState();
let provider = Social.provider;
let iconNames = Object.keys(provider.ambientNotificationIcons);
let iconBox = document.getElementById("social-status-iconbox");
let notifBox = document.getElementById("social-notification-box");
- let notifBrowsers = document.createDocumentFragment();
+ let panel = document.getElementById("social-notification-panel");
+ panel.hidden = false;
+ let notificationFrames = document.createDocumentFragment();
let iconContainers = document.createDocumentFragment();
for each(let name in iconNames) {
let icon = provider.ambientNotificationIcons[name];
- let notifBrowserId = "social-status-" + icon.name;
- let notifBrowser = document.getElementById(notifBrowserId);
- if (!notifBrowser) {
- notifBrowser = document.createElement("iframe");
- notifBrowser.setAttribute("type", "content");
- notifBrowser.setAttribute("id", notifBrowserId);
- notifBrowsers.appendChild(notifBrowser);
+ let notificationFrameId = "social-status-" + icon.name;
+ let notificationFrame = document.getElementById(notificationFrameId);
+ if (!notificationFrame) {
+ notificationFrame = document.createElement("iframe");
+ notificationFrame.setAttribute("type", "content");
+ notificationFrame.setAttribute("id", notificationFrameId);
+ notificationFrame.setAttribute("mozbrowser", "true");
+ notificationFrames.appendChild(notificationFrame);
}
- notifBrowser.setAttribute("origin", provider.origin);
- if (notifBrowser.getAttribute("src") != icon.contentPanel)
- notifBrowser.setAttribute("src", icon.contentPanel);
+ notificationFrame.setAttribute("origin", provider.origin);
+ if (notificationFrame.getAttribute("src") != icon.contentPanel)
+ notificationFrame.setAttribute("src", icon.contentPanel);
let iconId = "social-notification-icon-" + icon.name;
let iconContainer = document.getElementById(iconId);
let iconImage, iconCounter;
if (iconContainer) {
iconImage = iconContainer.getElementsByClassName("social-notification-icon-image")[0];
iconCounter = iconContainer.getElementsByClassName("social-notification-icon-counter")[0];
} else {
@@ -382,89 +561,65 @@ var SocialToolbar = {
iconCounter.classList.add("social-notification-icon-counter");
iconCounter.appendChild(document.createTextNode(""));
iconCounter = iconContainer.appendChild(iconCounter);
iconContainers.appendChild(iconContainer);
}
if (iconImage.getAttribute("src") != icon.iconURL)
iconImage.setAttribute("src", icon.iconURL);
- iconImage.setAttribute("notifBrowserId", notifBrowserId);
+ iconImage.setAttribute("notificationFrameId", notificationFrameId);
iconCounter.collapsed = !icon.counter;
iconCounter.firstChild.textContent = icon.counter || "";
}
- notifBox.appendChild(notifBrowsers);
+ notifBox.appendChild(notificationFrames);
iconBox.appendChild(iconContainers);
-
- let browserIter = notifBox.firstElementChild;
- while (browserIter) {
- browserIter.docShell.isAppTab = true;
- browserIter = browserIter.nextElementSibling;
- }
},
showAmbientPopup: function SocialToolbar_showAmbientPopup(iconContainer) {
let iconImage = iconContainer.firstChild;
let panel = document.getElementById("social-notification-panel");
let notifBox = document.getElementById("social-notification-box");
- let notifBrowser = document.getElementById(iconImage.getAttribute("notifBrowserId"));
-
- panel.hidden = false;
+ let notificationFrame = document.getElementById(iconImage.getAttribute("notificationFrameId"));
- function sizePanelToContent() {
- // FIXME: bug 764787: Maybe we can use nsIDOMWindowUtils.getRootBounds() here?
- // Need to handle dynamic sizing
- let doc = notifBrowser.contentDocument;
- if (!doc) {
- return;
- }
- // "notif" is an implementation detail that we should get rid of
- // eventually
- let body = doc.getElementById("notif") || doc.body;
- if (!body || !body.firstChild) {
- return;
- }
-
- // Clear dimensions on all browsers so the panel size will
- // only use the selected browser.
- let browserIter = notifBox.firstElementChild;
- while (browserIter) {
- browserIter.hidden = (browserIter != notifBrowser);
- browserIter = browserIter.nextElementSibling;
- }
-
- let [height, width] = [body.firstChild.offsetHeight || 300, 330];
- notifBrowser.style.width = width + "px";
- notifBrowser.style.height = height + "px";
+ // Clear dimensions on all browsers so the panel size will
+ // only use the selected browser.
+ let frameIter = notifBox.firstElementChild;
+ while (frameIter) {
+ frameIter.collapsed = (frameIter != notificationFrame);
+ frameIter = frameIter.nextElementSibling;
}
- sizePanelToContent();
-
function dispatchPanelEvent(name) {
- let evt = notifBrowser.contentDocument.createEvent("CustomEvent");
+ let evt = notificationFrame.contentDocument.createEvent("CustomEvent");
evt.initCustomEvent(name, true, true, {});
- notifBrowser.contentDocument.documentElement.dispatchEvent(evt);
+ notificationFrame.contentDocument.documentElement.dispatchEvent(evt);
}
- panel.addEventListener("popuphiding", function onpopuphiding() {
- panel.removeEventListener("popuphiding", onpopuphiding);
+ panel.addEventListener("popuphidden", function onpopuphiding() {
+ panel.removeEventListener("popuphidden", onpopuphiding);
SocialToolbar.button.removeAttribute("open");
+ notificationFrame.docShell.isActive = false;
dispatchPanelEvent("socialFrameHide");
});
panel.addEventListener("popupshown", function onpopupshown() {
panel.removeEventListener("popupshown", onpopupshown);
SocialToolbar.button.setAttribute("open", "true");
- if (notifBrowser.contentDocument.readyState == "complete") {
+ notificationFrame.docShell.isActive = true;
+ notificationFrame.docShell.isAppTab = true;
+ if (notificationFrame.contentDocument.readyState == "complete") {
+ sizeSocialPanelToContent(notificationFrame);
dispatchPanelEvent("socialFrameShow");
} else {
// first time load, wait for load and dispatch after load
- notifBrowser.addEventListener("load", function panelBrowserOnload(e) {
- notifBrowser.removeEventListener("load", panelBrowserOnload, true);
+ notificationFrame.addEventListener("load", function panelBrowserOnload(e) {
+ notificationFrame.removeEventListener("load", panelBrowserOnload, true);
+ sizeSocialPanelToContent(notificationFrame);
setTimeout(function() {
dispatchPanelEvent("socialFrameShow");
}, 0);
}, true);
}
});
panel.openPopup(iconImage, "bottomcenter topleft", 0, 0, false, false);
--- a/browser/base/content/browser-tabPreviews.js
+++ b/browser/base/content/browser-tabPreviews.js
@@ -184,19 +184,24 @@ var ctrlTab = {
// Rotate the list until the selected tab is first
while (!list[0].selected)
list.push(list.shift());
list = list.filter(function (tab) !tab.closing);
if (this.recentlyUsedLimit != 0) {
- let recentlyUsedTabs = this._recentlyUsedTabs;
- if (this.recentlyUsedLimit > 0)
- recentlyUsedTabs = this._recentlyUsedTabs.slice(0, this.recentlyUsedLimit);
+ let recentlyUsedTabs = [];
+ for (let tab of this._recentlyUsedTabs) {
+ if (!tab.hidden && !tab.closing) {
+ recentlyUsedTabs.push(tab);
+ if (this.recentlyUsedLimit > 0 && recentlyUsedTabs.length >= this.recentlyUsedLimit)
+ break;
+ }
+ }
for (let i = recentlyUsedTabs.length - 1; i >= 0; i--) {
list.splice(list.indexOf(recentlyUsedTabs[i]), 1);
list.unshift(recentlyUsedTabs[i]);
}
}
return this._tabList = list;
},
--- a/browser/base/content/browser-thumbnails.js
+++ b/browser/base/content/browser-thumbnails.js
@@ -31,30 +31,32 @@ let gBrowserThumbnails = {
_tabEvents: ["TabClose", "TabSelect"],
init: function Thumbnails_init() {
try {
if (Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled"))
return;
} catch (e) {}
+ PageThumbs.addExpirationFilter(this);
gBrowser.addTabsProgressListener(this);
Services.prefs.addObserver(this.PREF_DISK_CACHE_SSL, this, false);
this._sslDiskCacheEnabled =
Services.prefs.getBoolPref(this.PREF_DISK_CACHE_SSL);
this._tabEvents.forEach(function (aEvent) {
gBrowser.tabContainer.addEventListener(aEvent, this, false);
}, this);
this._timeouts = new WeakMap();
},
uninit: function Thumbnails_uninit() {
+ PageThumbs.removeExpirationFilter(this);
gBrowser.removeTabsProgressListener(this);
Services.prefs.removeObserver(this.PREF_DISK_CACHE_SSL, this);
this._tabEvents.forEach(function (aEvent) {
gBrowser.tabContainer.removeEventListener(aEvent, this, false);
}, this);
},
@@ -75,16 +77,21 @@ let gBrowserThumbnails = {
}
},
observe: function Thumbnails_observe() {
this._sslDiskCacheEnabled =
Services.prefs.getBoolPref(this.PREF_DISK_CACHE_SSL);
},
+ filterForThumbnailExpiration:
+ function Thumbnails_filterForThumbnailExpiration(aCallback) {
+ aCallback([browser.currentURI.spec for (browser of gBrowser.browsers)]);
+ },
+
/**
* 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);
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -59,16 +59,26 @@ tabbrowser {
display: none;
}
.tabbrowser-tabs[positionpinnedtabs] > .tabbrowser-tab[pinned] {
position: fixed !important;
display: block; /* position:fixed already does this (bug 579776), but let's be explicit */
}
+.tabbrowser-tabs[movingtab] > .tabbrowser-tab[selected] {
+ position: relative;
+ z-index: 2;
+ pointer-events: none; /* avoid blocking dragover events on scroll buttons */
+}
+
+.tabbrowser-tabs[movingtab] > .tabbrowser-tab[fadein]:not([selected]) {
+ transition: transform 200ms ease-out;
+}
+
#alltabs-popup {
-moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-alltabs-popup");
}
toolbar[printpreview="true"] {
-moz-binding: url("chrome://global/content/printPreviewBindings.xml#printpreviewtoolbar");
}
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -355,17 +355,17 @@ const gSessionHistoryObserver = {
return;
var backCommand = document.getElementById("Browser:Back");
backCommand.setAttribute("disabled", "true");
var fwdCommand = document.getElementById("Browser:Forward");
fwdCommand.setAttribute("disabled", "true");
// Hide session restore button on about:home
- window.messageManager.sendAsyncMessage("Browser:HideSessionRestoreButton");
+ window.messageManager.broadcastAsyncMessage("Browser:HideSessionRestoreButton");
if (gURLBar) {
// Clear undo history of the URL bar
gURLBar.editor.transactionManager.clear()
}
}
};
@@ -1015,16 +1015,17 @@ var gBrowserInit = {
gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false);
gBrowser.addEventListener("PluginNotFound", gPluginHandler, true);
gBrowser.addEventListener("PluginCrashed", gPluginHandler, true);
gBrowser.addEventListener("PluginBlocklisted", gPluginHandler, true);
gBrowser.addEventListener("PluginOutdated", gPluginHandler, true);
gBrowser.addEventListener("PluginDisabled", gPluginHandler, true);
gBrowser.addEventListener("PluginClickToPlay", gPluginHandler, true);
+ gBrowser.addEventListener("PluginPlayPreview", gPluginHandler, true);
gBrowser.addEventListener("PluginVulnerableUpdatable", gPluginHandler, true);
gBrowser.addEventListener("PluginVulnerableNoUpdate", gPluginHandler, true);
gBrowser.addEventListener("NewPluginInstalled", gPluginHandler.newPluginInstalled, true);
#ifdef XP_MACOSX
gBrowser.addEventListener("npapi-carbon-event-model-failure", gPluginHandler, true);
#endif
Services.obs.addObserver(gPluginHandler.pluginCrashed, "plugin-crashed", false);
@@ -1234,17 +1235,16 @@ var gBrowserInit = {
// Misc. inits.
CombinedStopReload.init();
allTabs.readPref();
TabsOnTop.init();
BookmarksMenuButton.init();
TabsInTitlebar.init();
gPrivateBrowsingUI.init();
- DownloadsButton.initializePlaceholder();
retrieveToolbarIconsizesFromTheme();
gDelayedStartupTimeoutId = setTimeout(this._delayedStartup.bind(this), 0, isLoadingBlank, mustLoadSidebar);
gStartupRan = true;
},
_delayedStartup: function(isLoadingBlank, mustLoadSidebar) {
let tmp = {};
@@ -1281,19 +1281,17 @@ var gBrowserInit = {
if (mustLoadSidebar) {
let sidebar = document.getElementById("sidebar");
let sidebarBox = document.getElementById("sidebar-box");
sidebar.setAttribute("src", sidebarBox.getAttribute("src"));
}
UpdateUrlbarSearchSplitterState();
- if (isLoadingBlank && gURLBar)
- gURLBar.focus();
- if (!isLoadingBlank || !gURLBar || !gURLBar.focused)
+ if (!isLoadingBlank || !focusAndSelectUrlBar())
gBrowser.selectedBrowser.focus();
gNavToolbox.customizeDone = BrowserToolboxCustomizeDone;
gNavToolbox.customizeChange = BrowserToolboxCustomizeChange;
// Set up Sanitize Item
this._initializeSanitizer();
@@ -1985,17 +1983,17 @@ function loadOneOrMoreURIs(aURIString)
}
function focusAndSelectUrlBar() {
if (gURLBar) {
if (window.fullScreen)
FullScreen.mouseoverToggle(true);
gURLBar.focus();
- if (gURLBar.focused) {
+ if (document.activeElement == gURLBar.inputField) {
gURLBar.select();
return true;
}
}
return false;
}
function openLocation() {
@@ -3124,39 +3122,16 @@ var newWindowButtonObserver = {
url = getShortcutOrURI(url, postData);
if (url) {
// allow third-party services to fixup this URL
openNewWindowWith(url, null, postData.value, true);
}
}
}
-var DownloadsButtonDNDObserver = {
- onDragOver: function (aEvent)
- {
- var types = aEvent.dataTransfer.types;
- if (types.contains("text/x-moz-url") ||
- types.contains("text/uri-list") ||
- types.contains("text/plain"))
- aEvent.preventDefault();
- },
-
- onDragExit: function (aEvent)
- {
- },
-
- onDrop: function (aEvent)
- {
- let name = { };
- let url = browserDragAndDrop.drop(aEvent, name);
- if (url)
- saveURL(url, name, null, true, true);
- }
-}
-
const DOMLinkHandler = {
handleEvent: function (event) {
switch (event.type) {
case "DOMLinkAdded":
this.onLinkAdded(event);
break;
}
},
@@ -3345,17 +3320,17 @@ const BrowserSearch = {
return;
}
#endif
var searchBar = this.searchBar;
if (searchBar && window.fullScreen)
FullScreen.mouseoverToggle(true);
if (searchBar)
searchBar.focus();
- if (searchBar && searchBar.textbox.focused) {
+ if (searchBar && document.activeElement == searchBar.textbox.inputField) {
searchBar.select();
} else {
openUILinkIn(Services.search.defaultEngine.searchForm, "current");
}
},
/**
* Loads a search results page, given a set of search terms. Uses the current
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -187,33 +187,33 @@
<hbox flex="1">
<image src="chrome://browser/content/social-icon.png" class="popup-notification-icon"/>
<vbox flex="1">
<description id="social-activation-message" class="popup-notification-description"/>
<spacer flex="1"/>
<hbox pack="end" align="center" class="popup-notification-button-container">
#ifdef XP_UNIX
<button id="social-undoactivation-button"
- label="&social.activated.button.label;"
- accesskey="&social.activated.button.accesskey;"
+ label="&social.activated.undobutton.label;"
+ accesskey="&social.activated.undobutton.accesskey;"
onclick="SocialUI.undoActivation();"/>
<button default="true"
autofocus="autofocus"
label="&social.ok.label;"
accesskey="&social.ok.accesskey;"
oncommand="SocialUI.notificationPanel.hidePopup();"/>
#else
<button default="true"
autofocus="autofocus"
label="&social.ok.label;"
accesskey="&social.ok.accesskey;"
oncommand="SocialUI.notificationPanel.hidePopup();"/>
<button id="social-undoactivation-button"
- label="&social.activated.button.label;"
- accesskey="&social.activated.button.accesskey;"
+ label="&social.activated.undobutton.label;"
+ accesskey="&social.activated.undobutton.accesskey;"
onclick="SocialUI.undoActivation();"/>
#endif
</hbox>
</vbox>
</hbox>
</panel>
<panel id="editSharePopup"
@@ -266,16 +266,23 @@
command="Social:UnsharePage"/>
#endif
</hbox>
</panel>
<panel id="social-notification-panel" type="arrow" hidden="true" noautofocus="true">
<box id="social-notification-box" flex="1"></box>
</panel>
+ <panel id="social-flyout-panel"
+ onpopupshown="SocialFlyout.onShown()"
+ onpopuphidden="SocialFlyout.onHidden()"
+ type="arrow"
+ hidden="true"
+ noautofocus="true"
+ position="topcenter topright"/>
<menupopup id="inspector-node-popup">
<menuitem id="inspectorHTMLCopyInner"
label="&inspectorHTMLCopyInner.label;"
accesskey="&inspectorHTMLCopyInner.accesskey;"
command="Inspector:CopyInner"/>
<menuitem id="inspectorHTMLCopyOuter"
label="&inspectorHTMLCopyOuter.label;"
@@ -510,17 +517,17 @@
<hbox class="titlebar-placeholder" type="caption-buttons" ordinal="1000"/>
#endif
</toolbar>
<toolbar id="nav-bar" class="toolbar-primary chromeclass-toolbar"
toolbarname="&navbarCmd.label;" accesskey="&navbarCmd.accesskey;"
fullscreentoolbar="true" mode="icons" customizable="true"
iconsize="large"
- defaultset="unified-back-forward-button,urlbar-container,reload-button,stop-button,search-container,home-button,bookmarks-menu-button-container,window-controls"
+ defaultset="unified-back-forward-button,urlbar-container,reload-button,stop-button,search-container,downloads-button,home-button,bookmarks-menu-button-container,window-controls"
context="toolbar-context-menu">
<toolbaritem id="unified-back-forward-button" class="chromeclass-toolbar-additional"
context="backForwardMenu" removable="true"
forwarddisabled="true"
title="&backForwardItem.title;">
<toolbarbutton id="back-button" class="toolbarbutton-1"
label="&backCmd.label;"
@@ -669,18 +676,16 @@
<image id="social-statusarea-user-portrait"/>
<vbox>
<label id="social-statusarea-notloggedin"
value="&social.notLoggedIn.label;"/>
<button id="social-statusarea-username"
oncommand="SocialUI.showProfile(); document.getElementById('social-statusarea-popup').hidePopup();"/>
</vbox>
</hbox>
- <menuitem id="social-remove-menuitem"
- oncommand="Social.active = false;"/>
<menuitem id="social-toggle-sidebar-menuitem"
type="checkbox"
autocheck="false"
command="Social:ToggleSidebar"
label="&social.toggleSidebar.label;"
accesskey="&social.toggleSidebar.accesskey;"/>
</menupopup>
</button>
@@ -927,24 +932,24 @@
# Update primaryToolbarButtons in browser/themes/browserShared.inc when adding
# or removing default items with the toolbarbutton-1 class.
<toolbarbutton id="print-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
label="&printButton.label;" command="cmd_print"
tooltiptext="&printButton.tooltip;"/>
<!-- This is a placeholder for the Downloads Indicator. It is visible
- only during the customization of the toolbar or in the palette, and
- is replaced when customization is done. -->
+ during the customization of the toolbar, in the palette, and before
+ the Downloads Indicator overlay is loaded. -->
<toolbarbutton id="downloads-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
- observes="Tools:Downloads"
- ondrop="DownloadsButtonDNDObserver.onDrop(event)"
- ondragover="DownloadsButtonDNDObserver.onDragOver(event)"
- ondragenter="DownloadsButtonDNDObserver.onDragOver(event)"
- ondragexit="DownloadsButtonDNDObserver.onDragExit(event)"
+ oncommand="DownloadsIndicatorView.onCommand(event);"
+ ondrop="DownloadsIndicatorView.onDrop(event);"
+ ondragover="DownloadsIndicatorView.onDragOver(event);"
+ ondragenter="DownloadsIndicatorView.onDragOver(event);"
+ ondragleave="DownloadsIndicatorView.onDragLeave(event);"
label="&downloads.label;"
tooltiptext="&downloads.tooltip;"/>
<toolbarbutton id="history-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
observes="viewHistorySidebar" label="&historyButton.label;"
tooltiptext="&historyButton.tooltip;"/>
<toolbarbutton id="bookmarks-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
@@ -1065,16 +1070,17 @@
<splitter id="social-sidebar-splitter"
class="chromeclass-extrachrome sidebar-splitter"
observes="socialSidebarBroadcaster"/>
<vbox id="social-sidebar-box"
class="chromeclass-extrachrome"
observes="socialSidebarBroadcaster">
<browser id="social-sidebar-browser"
type="content"
+ disableglobalhistory="true"
flex="1"
style="min-width: 14em; width: 18em; max-width: 36em;"/>
</vbox>
<vbox id="browser-border-end" hidden="true" layer="true"/>
</hbox>
<hbox id="full-screen-warning-container" hidden="true" fadeout="true">
<hbox style="width: 100%;" pack="center"> <!-- Inner hbox needed due to bug 579776. -->
@@ -1103,34 +1109,16 @@
nowindowdrag="true"
hidden="true">
#ifdef XP_MACOSX
<toolbarbutton id="highlighter-closebutton"
class="devtools-closebutton"
oncommand="InspectorUI.closeInspectorUI(false);"
tooltiptext="&inspectCloseButton.tooltiptext;"/>
#endif
- <toolbarbutton id="inspector-option-toolbarbutton"
- type="menu"
- tabindex="0"
- tooltiptext="&inspectOptionButton.tooltiptext;">
- <menupopup id="inspector-option-popup"
- position="before_start">
- <menuitem id="inspectorToggleVeil"
- type="checkbox"
- label="&inspectorToggleVeil.label;"
- accesskey="&inspectorToggleVeil.accesskey;"
- command="Inspector:ToggleVeil"/>
- <menuitem id="inspectorToggleInfobar"
- type="checkbox"
- label="&inspectorToggleInfobar.label;"
- accesskey="&inspectorToggleInfobar.accesskey;"
- command="Inspector:ToggleInfobar"/>
- </menupopup>
- </toolbarbutton>
<toolbarbutton id="inspector-inspect-toolbutton"
class="devtools-toolbarbutton"
command="Inspector:Inspect"/>
<toolbarbutton id="inspector-treepanel-toolbutton"
class="devtools-toolbarbutton"
tabindex="0"
aria-label="&markupButton.arialabel;"
accesskey="&markupButton.accesskey;"
--- a/browser/base/content/highlighter.css
+++ b/browser/base/content/highlighter.css
@@ -7,58 +7,59 @@
}
#highlighter-controls {
position: absolute;
top: 0;
left: 0;
}
-#highlighter-veil-container {
+#highlighter-outline-container {
overflow: hidden;
+ position: relative;
}
-#highlighter-veil-container:not([dim]) > .highlighter-veil,
-#highlighter-veil-container:not([dim]) > hbox > .highlighter-veil {
- visibility: hidden;
+#highlighter-outline {
+ position: absolute;
}
-#highlighter-veil-container:not([disable-transitions]) > .highlighter-veil,
-#highlighter-veil-container:not([disable-transitions]) > #highlighter-veil-middlebox,
-#highlighter-veil-container:not([disable-transitions]) > #highlighter-veil-middlebox > .highlighter-veil,
-#highlighter-veil-container:not([disable-transitions]) > #highlighter-veil-middlebox > #highlighter-veil-transparentbox {
- transition-property: width, height;
+#highlighter-outline[hidden] {
+ opacity: 0;
+ pointer-events: none;
+ display: -moz-box;
+}
+
+#highlighter-outline:not([disable-transitions]) {
+ transition-property: opacity, top, left, width, height;
transition-duration: 0.1s;
transition-timing-function: linear;
}
-#highlighter-veil-bottombox,
-#highlighter-veil-rightbox {
- -moz-box-flex: 1;
-}
-
-#highlighter-veil-middlebox:-moz-locale-dir(rtl) {
- -moz-box-direction: reverse;
-}
-
.inspector-breadcrumbs-button {
direction: ltr;
}
/*
* Node Infobar
*/
#highlighter-nodeinfobar-container {
position: absolute;
max-width: 95%;
}
-#highlighter-nodeinfobar-container:not([disable-transitions]) {
- transition-property: top, left;
+#highlighter-nodeinfobar-container[hidden] {
+ opacity: 0;
+ pointer-events: none;
+ display: -moz-box;
+}
+
+#highlighter-nodeinfobar-container:not([disable-transitions]),
+#highlighter-nodeinfobar-container[disable-transitions][force-transitions] {
+ transition-property: transform, opacity, top, left;
transition-duration: 0.1s;
transition-timing-function: linear;
}
#highlighter-nodeinfobar-text {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
--- a/browser/base/content/socialchat.xml
+++ b/browser/base/content/socialchat.xml
@@ -20,16 +20,43 @@
xbl:inherits="src,origin,collapsed=minimized" type="content"/>
</content>
<implementation implements="nsIDOMEventListener">
<field name="iframe" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "iframe");
</field>
+ <property name="minimized">
+ <getter>
+ return this.getAttribute("minimized") == "true";
+ </getter>
+ <setter>
+ this.isActive = !val;
+ if (val)
+ this.setAttribute("minimized", "true");
+ else
+ this.removeAttribute("minimized");
+ </setter>
+ </property>
+
+ <property name="isActive">
+ <getter>
+ return this.iframe.docShell.isActive;
+ </getter>
+ <setter>
+ this.iframe.docShell.isActive = !!val;
+
+ // let the chat frame know if it is being shown or hidden
+ let evt = this.iframe.contentDocument.createEvent("CustomEvent");
+ evt.initCustomEvent(val ? "socialFrameHide" : "socialFrameShow", true, true, {});
+ this.iframe.contentDocument.documentElement.dispatchEvent(evt);
+ </setter>
+ </property>
+
<method name="init">
<parameter name="aProvider"/>
<parameter name="aURL"/>
<parameter name="aCallback"/>
<body><![CDATA[
this._callback = aCallback;
this.setAttribute("origin", aProvider.origin);
this.setAttribute("src", aURL);
@@ -39,37 +66,29 @@
<method name="close">
<body><![CDATA[
this.parentNode.remove(this);
]]></body>
</method>
<method name="toggle">
<body><![CDATA[
- let type;
- if (this.getAttribute("minimized") == "true") {
- this.removeAttribute("minimized");
- type = "socialFrameShow";
- } else {
- this.setAttribute("minimized", "true");
- type = "socialFrameHide";
- }
- // let the chat frame know if it is being shown or hidden
- let evt = this.iframe.contentDocument.createEvent("CustomEvent");
- evt.initCustomEvent(type, true, true, {});
- this.iframe.contentDocument.documentElement.dispatchEvent(evt);
+ this.minimized = !this.minimized;
]]></body>
</method>
</implementation>
<handlers>
<handler event="focus" phase="capturing">
this.parentNode.selectedChat = this;
</handler>
- <handler event="DOMContentLoaded" action="if (this._callback) this._callback(this.iframe.contentWindow);"/>
+ <handler event="load"><![CDATA[
+ this.isActive = !this.minimized;
+ if (this._callback) this._callback(this.iframe.contentWindow);
+ ]]></handler>
<handler event="DOMTitleChanged" action="this.setAttribute('label', this.iframe.contentDocument.title);"/>
<handler event="DOMLinkAdded"><![CDATA[
// much of this logic is from DOMLinkHandler in browser.js
// this sets the presence icon for a chat user, we simply use favicon style updating
let link = event.originalTarget;
let rel = link.rel && link.rel.toLowerCase();
if (!link || !link.ownerDocument || !rel || !link.href)
return;
@@ -111,16 +130,17 @@
<getter>
return document.getAnonymousElementByAttribute(this, "anonid", "spacer").boxObject.width;
</getter>
</property>
<field name="selectedChat"/>
<field name="menuitemMap">new WeakMap()</field>
+ <field name="chatboxForURL">new Map();</field>
<property name="firstCollapsedChild">
<getter><![CDATA[
let child = this.lastChild;
while (child && !child.collapsed) {
child = child.previousSibling;
}
return child;
@@ -187,63 +207,84 @@
this.showChat(newChat);
]]></body>
</method>
<method name="collapseChat">
<parameter name="aChatbox"/>
<body><![CDATA[
aChatbox.collapsed = true;
+ aChatbox.isActive = false;
let menu = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "menuitem");
menu.setAttribute("label", aChatbox.iframe.contentDocument.title);
menu.chat = aChatbox;
this.menuitemMap.set(aChatbox, menu);
this.menupopup.appendChild(menu);
this.menupopup.parentNode.collapsed = false;
]]></body>
</method>
<method name="showChat">
<parameter name="aChatbox"/>
<body><![CDATA[
let menuitem = this.menuitemMap.get(aChatbox);
this.menuitemMap.delete(aChatbox);
this.menupopup.removeChild(menuitem);
aChatbox.collapsed = false;
+ aChatbox.isActive = !aChatbox.minimized;
]]></body>
</method>
<method name="remove">
<parameter name="aChatbox"/>
<body><![CDATA[
if (this.selectedChat == aChatbox) {
this.selectedChat = aChatbox.previousSibling ? aChatbox.previousSibling : aChatbox.nextSibling
}
this.removeChild(aChatbox);
this.resize();
+ this.chatboxForURL.delete(aChatbox.getAttribute('src'));
]]></body>
</method>
<method name="removeAll">
<body><![CDATA[
while (this.firstChild) {
this.removeChild(this.firstChild);
}
+ this.chatboxForURL = new Map();
]]></body>
</method>
- <method name="newChat">
+ <method name="openChat">
<parameter name="aProvider"/>
<parameter name="aURL"/>
<parameter name="aCallback"/>
+ <parameter name="aMode"/>
<body><![CDATA[
- let cb = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "chatbox");
+ let cb = this.chatboxForURL.get(aURL);
+ if (cb) {
+ cb = cb.get();
+ if (cb.parentNode) {
+ // ensure this chatbox is visible
+ if (this.selectedChat != cb)
+ this.selectedChat = cb;
+ if (cb.collapsed)
+ this.showChat(cb);
+ return;
+ }
+ this.chatboxForURL.delete(aURL);
+ }
+ cb = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "chatbox");
+ if (aMode == "minimized")
+ cb.minimized = true;
this.selectedChat = cb;
- this.appendChild(cb);
+ this.insertBefore(cb, this.firstChild);
cb.init(aProvider, aURL, aCallback);
+ this.chatboxForURL.set(aURL, Cu.getWeakReference(cb));
]]></body>
</method>
</implementation>
<handlers>
<handler event="overflow"><![CDATA[
// make sure we're not getting an overflow from content
if (event.originalTarget != this.innerbox)
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -3122,16 +3122,137 @@
this.style.MozMarginStart = "";
}
this.mTabstrip.ensureElementIsVisible(this.selectedItem, false);
]]></body>
</method>
+ <method name="_animateTabMove">
+ <parameter name="event"/>
+ <body><![CDATA[
+ let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
+
+ if (this.getAttribute("movingtab") != "true") {
+ this.setAttribute("movingtab", "true");
+ this.selectedItem = draggedTab;
+ }
+
+ if (!("animLastScreenX" in draggedTab._dragData))
+ draggedTab._dragData.animLastScreenX = draggedTab._dragData.screenX;
+
+ let screenX = event.screenX;
+ if (screenX == draggedTab._dragData.animLastScreenX)
+ return;
+
+ let draggingRight = screenX > draggedTab._dragData.animLastScreenX;
+ draggedTab._dragData.animLastScreenX = screenX;
+
+ let rtl = (window.getComputedStyle(this).direction == "rtl");
+ let pinned = draggedTab.pinned;
+ let numPinned = this.tabbrowser._numPinnedTabs;
+ let tabs = this.tabbrowser.visibleTabs
+ .slice(pinned ? 0 : numPinned,
+ pinned ? numPinned : undefined);
+ if (rtl)
+ tabs.reverse();
+ let tabWidth = draggedTab.getBoundingClientRect().width;
+
+ // Move the dragged tab based on the mouse position.
+
+ let leftTab = tabs[0];
+ let rightTab = tabs[tabs.length - 1];
+ let tabScreenX = draggedTab.boxObject.screenX;
+ let translateX = screenX - draggedTab._dragData.screenX;
+ if (!pinned)
+ translateX += this.mTabstrip.scrollPosition - draggedTab._dragData.scrollX;
+ let leftBound = leftTab.boxObject.screenX - tabScreenX;
+ let rightBound = (rightTab.boxObject.screenX + rightTab.boxObject.width) -
+ (tabScreenX + tabWidth);
+ translateX = Math.max(translateX, leftBound);
+ translateX = Math.min(translateX, rightBound);
+ draggedTab.style.transform = "translateX(" + translateX + "px)";
+
+ // Determine what tab we're dragging over.
+ // * Point of reference is the center of the dragged tab. If that
+ // point touches a background tab, the dragged tab would take that
+ // tab's position when dropped.
+ // * We're doing a binary search in order to reduce the amount of
+ // tabs we need to check.
+
+ let tabCenter = tabScreenX + translateX + tabWidth / 2;
+ let newIndex = -1;
+ let oldIndex = "animDropIndex" in draggedTab._dragData ?
+ draggedTab._dragData.animDropIndex : draggedTab._tPos;
+ let low = 0;
+ let high = tabs.length - 1;
+ while (low <= high) {
+ let mid = Math.floor((low + high) / 2);
+ if (tabs[mid] == draggedTab &&
+ ++mid > high)
+ break;
+ let boxObject = tabs[mid].boxObject;
+ let screenX = boxObject.screenX + getTabShift(tabs[mid], oldIndex);
+ if (screenX > tabCenter) {
+ high = mid - 1;
+ } else if (screenX + boxObject.width < tabCenter) {
+ low = mid + 1;
+ } else {
+ newIndex = tabs[mid]._tPos;
+ break;
+ }
+ }
+ if (newIndex >= oldIndex)
+ newIndex++;
+ if (newIndex < 0 || newIndex == oldIndex)
+ return;
+ draggedTab._dragData.animDropIndex = newIndex;
+
+ // Shift background tabs to leave a gap where the dragged tab
+ // would currently be dropped.
+
+ for (let tab of tabs) {
+ if (tab != draggedTab) {
+ let shift = getTabShift(tab, newIndex);
+ tab.style.transform = shift ? "translateX(" + shift + "px)" : "";
+ }
+ }
+
+ function getTabShift(tab, dropIndex) {
+ if (tab._tPos < draggedTab._tPos && tab._tPos >= dropIndex)
+ return rtl ? -tabWidth : tabWidth;
+ if (tab._tPos > draggedTab._tPos && tab._tPos < dropIndex)
+ return rtl ? tabWidth : -tabWidth;
+ return 0;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_finishAnimateTabMove">
+ <parameter name="event"/>
+ <body><![CDATA[
+ if (this.getAttribute("movingtab") != "true")
+ return;
+
+ let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
+ if ("animDropIndex" in draggedTab._dragData) {
+ let newIndex = draggedTab._dragData.animDropIndex;
+ if (newIndex > draggedTab._tPos)
+ newIndex--;
+ this.tabbrowser.moveTabTo(draggedTab, newIndex);
+ }
+
+ for (let tab of this.tabbrowser.visibleTabs)
+ tab.style.transform = "";
+
+ this.removeAttribute("movingtab");
+ ]]></body>
+ </method>
+
<method name="handleEvent">
<parameter name="aEvent"/>
<body><![CDATA[
switch (aEvent.type) {
case "load":
this.updateVisibility();
break;
case "resize":
@@ -3253,52 +3374,35 @@
var types = dt.mozTypesAt(0);
var sourceNode = null;
// tabs are always added as the first type
if (types[0] == TAB_DROP_TYPE) {
var sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
if (sourceNode instanceof XULElement &&
sourceNode.localName == "tab" &&
- (sourceNode.parentNode == this ||
- (sourceNode.ownerDocument.defaultView instanceof ChromeWindow &&
- sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser"))) {
- if (sourceNode.parentNode == this &&
- (event.screenX >= sourceNode.boxObject.screenX &&
- event.screenX <= (sourceNode.boxObject.screenX +
- sourceNode.boxObject.width))) {
- return dt.effectAllowed = "none";
- }
-
- return dt.effectAllowed = "copyMove";
+ sourceNode.ownerDocument.defaultView instanceof ChromeWindow &&
+ sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser" &&
+ sourceNode.ownerDocument.defaultView.gBrowser.tabContainer == sourceNode.parentNode) {
+#ifdef XP_MACOSX
+ return dt.effectAllowed = event.altKey ? "copy" : "move";
+#else
+ return dt.effectAllowed = event.ctrlKey ? "copy" : "move";
+#endif
}
}
if (browserDragAndDrop.canDropLink(event)) {
// Here we need to do this manually
return dt.effectAllowed = dt.dropEffect = "link";
}
return dt.effectAllowed = "none";
]]></body>
</method>
- <method name="_continueScroll">
- <parameter name="event"/>
- <body><![CDATA[
- // Workaround for bug 481904: Dragging a tab stops scrolling at
- // the tab's position when dragging to the first/last tab and back.
- var t = this.selectedItem;
- if (event.screenX >= t.boxObject.screenX &&
- event.screenX <= t.boxObject.screenX + t.boxObject.width &&
- event.screenY >= t.boxObject.screenY &&
- event.screenY <= t.boxObject.screenY + t.boxObject.height)
- this.mTabstrip.ensureElementIsVisible(t);
- ]]></body>
- </method>
-
<method name="_handleNewTab">
<parameter name="tab"/>
<body><![CDATA[
if (tab.parentNode != this)
return;
tab._fullyOpen = true;
this.adjustTabstrip();
@@ -3449,50 +3553,46 @@
// may result in an "internet shortcut"
dt.mozSetDataAt("text/x-moz-text-internal", spec, 0);
// Set the cursor to an arrow during tab drags.
dt.mozCursor = "default";
// Create a canvas to which we capture the current tab.
let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ let browser = tab.linkedBrowser;
canvas.mozOpaque = true;
-
- // We want drag images to be about 1/6th of the available screen width.
- const widthFactor = 0.1739; // 1:5.75 inverse
- canvas.width = Math.ceil(screen.availWidth * widthFactor);
-
- // Maintain a 16:9 aspect ratio for drag images.
- const aspectRatio = 0.5625; // 16:9 inverse
- canvas.height = Math.round(canvas.width * aspectRatio);
-
- let browser = tab.linkedBrowser;
+ canvas.width = 160;
+ canvas.height = 90;
PageThumbs.captureToCanvas(browser.contentWindow, canvas);
dt.setDragImage(canvas, 0, 0);
- // _dragOffsetX/Y give the coordinates that the mouse should be
+ // _dragData.offsetX/Y give the coordinates that the mouse should be
// positioned relative to the corner of the new window created upon
// dragend such that the mouse appears to have the same position
// relative to the corner of the dragged tab.
function clientX(ele) ele.getBoundingClientRect().left;
let tabOffsetX = clientX(tab) -
clientX(this.children[0].pinned ? this.children[0] : this);
- tab._dragOffsetX = event.screenX - window.screenX - tabOffsetX;
- tab._dragOffsetY = event.screenY - window.screenY;
+ tab._dragData = {
+ offsetX: event.screenX - window.screenX - tabOffsetX,
+ offsetY: event.screenY - window.screenY,
+ scrollX: this.mTabstrip.scrollPosition,
+ screenX: event.screenX
+ };
event.stopPropagation();
]]></handler>
<handler event="dragover"><![CDATA[
var effects = this._setEffectAllowedForDataTransfer(event);
var ind = this._tabDropIndicator;
if (effects == "" || effects == "none") {
ind.collapsed = true;
- this._continueScroll(event);
return;
}
event.preventDefault();
event.stopPropagation();
var tabStrip = this.mTabstrip;
var ltr = (window.getComputedStyle(this, null).direction == "ltr");
@@ -3509,16 +3609,25 @@
case "scrollbutton-down":
pixelsToScroll = tabStrip.scrollIncrement;
break;
}
if (pixelsToScroll)
tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll);
}
+ if (effects == "move" &&
+ this == event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0).parentNode) {
+ ind.collapsed = true;
+ this._animateTabMove(event);
+ return;
+ }
+
+ this._finishAnimateTabMove(event);
+
if (effects == "link") {
let tab = this._getDragTargetTab(event);
if (tab) {
if (!this._dragTime)
this._dragTime = Date.now();
if (Date.now() >= this._dragTime + this._dragOverDelay)
this.selectedItem = tab;
ind.collapsed = true;
@@ -3576,53 +3685,40 @@
draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
// not our drop then
if (!draggedTab)
return;
}
this._tabDropIndicator.collapsed = true;
event.stopPropagation();
-
- if (draggedTab && (dropEffect == "copy" ||
- draggedTab.parentNode == this)) {
+ if (draggedTab && dropEffect == "copy") {
+ // copy the dropped tab (wherever it's from)
let newIndex = this._getDropIndex(event);
- if (dropEffect == "copy") {
- // copy the dropped tab (wherever it's from)
- let newTab = this.tabbrowser.duplicateTab(draggedTab);
- this.tabbrowser.moveTabTo(newTab, newIndex);
- if (draggedTab.parentNode != this || event.shiftKey)
- this.selectedItem = newTab;
- } else {
- // move the dropped tab
- if (newIndex > draggedTab._tPos)
- newIndex--;
-
- if (draggedTab.pinned) {
- if (newIndex >= this.tabbrowser._numPinnedTabs)
- this.tabbrowser.unpinTab(draggedTab);
- } else {
- if (newIndex <= this.tabbrowser._numPinnedTabs - 1)
- this.tabbrowser.pinTab(draggedTab);
- }
-
- this.tabbrowser.moveTabTo(draggedTab, newIndex);
- }
+ let newTab = this.tabbrowser.duplicateTab(draggedTab);
+ this.tabbrowser.moveTabTo(newTab, newIndex);
+ if (draggedTab.parentNode != this || event.shiftKey)
+ this.selectedItem = newTab;
+ } else if (draggedTab && draggedTab.parentNode == this) {
+ this._finishAnimateTabMove(event);
} else if (draggedTab) {
// swap the dropped tab with a new one we create and then close
// it in the other window (making it seem to have moved between
// windows)
let newIndex = this._getDropIndex(event);
let newTab = this.tabbrowser.addTab("about:blank");
let newBrowser = this.tabbrowser.getBrowserForTab(newTab);
// Stop the about:blank load
newBrowser.stop();
// make sure it has a docshell
newBrowser.docShell;
+ let numPinned = this.tabbrowser._numPinnedTabs;
+ if (newIndex < numPinned || draggedTab.pinned && newIndex == numPinned)
+ this.tabbrowser.pinTab(newTab);
this.tabbrowser.moveTabTo(newTab, newIndex);
this.tabbrowser.swapBrowsersAndCloseOther(newTab, draggedTab);
// We need to select the tab after we've done
// swapBrowsersAndCloseOther, so that the updateCurrentBrowser
// it triggers will correctly update our URL bar.
this.tabbrowser.selectedTab = newTab;
@@ -3655,66 +3751,65 @@
if (!bgLoad)
this.selectedItem = tab;
} catch(ex) {
// Just ignore invalid urls
}
}
}
- // these offsets are only used in dragend, but we need to free them here
- // as well
if (draggedTab) {
- delete draggedTab._dragOffsetX;
- delete draggedTab._dragOffsetY;
+ delete draggedTab._dragData;
}
]]></handler>
<handler event="dragend"><![CDATA[
// Note: while this case is correctly handled here, this event
// isn't dispatched when the tab is moved within the tabstrip,
// see bug 460801.
- // * mozUserCancelled = the user pressed ESC to cancel the drag
+ this._finishAnimateTabMove(event);
+
var dt = event.dataTransfer;
- if (dt.mozUserCancelled || dt.dropEffect != "none")
+ var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
+ if (dt.mozUserCancelled || dt.dropEffect != "none") {
+ delete draggedTab._dragData;
return;
+ }
// Disable detach within the browser toolbox
var eX = event.screenX;
var eY = event.screenY;
var wX = window.screenX;
// check if the drop point is horizontally within the window
if (eX > wX && eX < (wX + window.outerWidth)) {
let bo = this.mTabstrip.boxObject;
// also avoid detaching if the the tab was dropped too close to
// the tabbar (half a tab)
let endScreenY = bo.screenY + 1.5 * bo.height;
if (eY < endScreenY && eY > window.screenY)
return;
}
- var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
// screen.availLeft et. al. only check the screen that this window is on,
// but we want to look at the screen the tab is being dropped onto.
var sX = {}, sY = {}, sWidth = {}, sHeight = {};
Cc["@mozilla.org/gfx/screenmanager;1"]
.getService(Ci.nsIScreenManager)
.screenForRect(eX, eY, 1, 1)
.GetAvailRect(sX, sY, sWidth, sHeight);
// ensure new window entirely within screen
var winWidth = Math.min(window.outerWidth, sWidth.value);
var winHeight = Math.min(window.outerHeight, sHeight.value);
- var left = Math.min(Math.max(eX - draggedTab._dragOffsetX, sX.value),
+ var left = Math.min(Math.max(eX - draggedTab._dragData.offsetX, sX.value),
sX.value + sWidth.value - winWidth);
- var top = Math.min(Math.max(eY - draggedTab._dragOffsetY, sY.value),
+ var top = Math.min(Math.max(eY - draggedTab._dragData.offsetY, sY.value),
sY.value + sHeight.value - winHeight);
- delete draggedTab._dragOffsetX;
- delete draggedTab._dragOffsetY;
+ delete draggedTab._dragData;
if (this.tabbrowser.tabs.length == 1) {
// resize _before_ move to ensure the window fits the new screen. if
// the window is too large for its screen, the window manager may do
// automatic repositioning.
window.resizeTo(winWidth, winHeight);
window.moveTo(left, top);
window.focus();
@@ -3736,17 +3831,16 @@
// This does not work at all (see bug 458613)
var target = event.relatedTarget;
while (target && target != this)
target = target.parentNode;
if (target)
return;
this._tabDropIndicator.collapsed = true;
- this._continueScroll(event);
event.stopPropagation();
]]></handler>
</handlers>
</binding>
<!-- close-tab-button binding
This binding relies on the structure of the tabbrowser binding.
Therefore it should only be used as a child of the tab or the tabs
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -59,19 +59,16 @@ endif
# 480169)
# browser_drag.js is disabled, as it needs to be updated for the new behavior from bug 320638.
# browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
# browser_pageInfo.js + feed_tab.html is disabled for leaking (bug 767896)
-# browser_social_shareButton.js is disabled for not properly
-# tearing down the social providers (bug 780010).
-
_BROWSER_FILES = \
head.js \
browser_typeAheadFind.js \
browser_keywordSearch.js \
browser_allTabsPanel.js \
browser_alltabslistener.js \
browser_bug304198.js \
title_test.svg \
@@ -177,16 +174,17 @@ endif
browser_hide_removing.js \
browser_overflowScroll.js \
browser_locationBarCommand.js \
browser_locationBarExternalLoad.js \
browser_page_style_menu.js \
browser_pinnedTabs.js \
browser_plainTextLinks.js \
browser_pluginnotification.js \
+ browser_pluginplaypreview.js \
browser_relatedTabs.js \
browser_sanitize-passwordDisabledHosts.js \
browser_sanitize-sitepermissions.js \
browser_sanitize-timespans.js \
browser_clearplugindata.js \
browser_clearplugindata.html \
browser_clearplugindata_noage.html \
browser_popupUI.js \
@@ -258,23 +256,27 @@ endif
browser_minimize.js \
browser_aboutSyncProgress.js \
browser_middleMouse_inherit.js \
redirect_bug623155.sjs \
browser_tabDrop.js \
browser_lastAccessedTab.js \
browser_bug734076.js \
browser_social_toolbar.js \
+ browser_social_shareButton.js \
browser_social_sidebar.js \
+ browser_social_flyout.js \
browser_social_mozSocial_API.js \
browser_social_isVisible.js \
browser_social_chatwindow.js \
social_panel.html \
+ social_share_image.png \
social_sidebar.html \
social_chat.html \
+ social_flyout.html \
social_window.html \
social_worker.js \
$(NULL)
ifneq (cocoa,$(MOZ_WIDGET_TOOLKIT))
_BROWSER_FILES += \
browser_bug462289.js \
$(NULL)
--- a/browser/base/content/test/browser_pluginnotification.js
+++ b/browser/base/content/test/browser_pluginnotification.js
@@ -2,28 +2,16 @@ var rootDir = getRootDirectory(gTestPath
const gTestRoot = rootDir;
const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
var gTestBrowser = null;
var gNextTest = null;
var gClickToPlayPluginActualEvents = 0;
var gClickToPlayPluginExpectedEvents = 5;
-function get_test_plugin() {
- var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
- var tags = ph.getPluginTags();
-
- // Find the test plugin
- for (var i = 0; i < tags.length; i++) {
- if (tags[i].name == "Test Plug-in")
- return tags[i];
- }
- ok(false, "Unable to find plugin");
-}
-
Components.utils.import("resource://gre/modules/Services.jsm");
// This listens for the next opened tab and checks it is of the right url.
// opencallback is called when the new tab is fully loaded
// closecallback is called when the tab is closed
function TabOpenListener(url, opencallback, closecallback) {
this.url = url;
this.opencallback = opencallback;
@@ -112,31 +100,31 @@ function test1() {
ok("application/x-unknown" in gTestBrowser.missingPlugins, "Test 1, Should know about application/x-unknown");
ok(!("application/x-test" in gTestBrowser.missingPlugins), "Test 1, Should not know about application/x-test");
var pluginNode = gTestBrowser.contentDocument.getElementById("unknown");
ok(pluginNode, "Test 1, Found plugin in page");
var objLoadingContent = pluginNode.QueryInterface(Ci.nsIObjectLoadingContent);
is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED, "Test 1, plugin fallback type should be PLUGIN_UNSUPPORTED");
- var plugin = get_test_plugin();
+ var plugin = getTestPlugin();
ok(plugin, "Should have a test plugin");
plugin.disabled = false;
plugin.blocklisted = false;
prepareTest(test2, gTestRoot + "plugin_test.html");
}
// Tests a page with a working plugin in it.
function test2() {
var notificationBox = gBrowser.getNotificationBox(gTestBrowser);
ok(!notificationBox.getNotificationWithValue("missing-plugins"), "Test 2, Should not have displayed the missing plugin notification");
ok(!notificationBox.getNotificationWithValue("blocked-plugins"), "Test 2, Should not have displayed the blocked plugin notification");
ok(!gTestBrowser.missingPlugins, "Test 2, Should not be a missing plugin list");
- var plugin = get_test_plugin();
+ var plugin = getTestPlugin();
ok(plugin, "Should have a test plugin");
plugin.disabled = true;
prepareTest(test3, gTestRoot + "plugin_test.html");
}
// Tests a page with a disabled plugin in it.
function test3() {
var notificationBox = gBrowser.getNotificationBox(gTestBrowser);
@@ -157,17 +145,17 @@ function test3() {
}
function test4(tab, win) {
is(win.wrappedJSObject.gViewController.currentViewId, "addons://list/plugin", "Test 4, Should have displayed the plugins pane");
gBrowser.removeTab(tab);
}
function prepareTest5() {
- var plugin = get_test_plugin();
+ var plugin = getTestPlugin();
plugin.disabled = false;
plugin.blocklisted = true;
prepareTest(test5, gTestRoot + "plugin_test.html");
}
// Tests a page with a blocked plugin in it.
function test5() {
var notificationBox = gBrowser.getNotificationBox(gTestBrowser);
@@ -200,17 +188,17 @@ function test6() {
function test7() {
var notificationBox = gBrowser.getNotificationBox(gTestBrowser);
ok(notificationBox.getNotificationWithValue("missing-plugins"), "Test 7, Should have displayed the missing plugin notification");
ok(!notificationBox.getNotificationWithValue("blocked-plugins"), "Test 7, Should not have displayed the blocked plugin notification");
ok(gTestBrowser.missingPlugins, "Test 7, Should be a missing plugin list");
ok("application/x-unknown" in gTestBrowser.missingPlugins, "Test 7, Should know about application/x-unknown");
ok("application/x-test" in gTestBrowser.missingPlugins, "Test 7, Should know about application/x-test");
- var plugin = get_test_plugin();
+ var plugin = getTestPlugin();
plugin.disabled = false;
plugin.blocklisted = false;
Services.prefs.setBoolPref("plugins.click_to_play", true);
prepareTest(test8, gTestRoot + "plugin_test.html");
}
// Tests a page with a working plugin that is click-to-play
@@ -456,17 +444,17 @@ function test13c() {
}
// Tests that the plugin's "activated" property is true for working plugins with click-to-play disabled.
function test14() {
var plugin = gTestBrowser.contentDocument.getElementById("test1");
var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
ok(objLoadingContent.activated, "Test 14, Plugin should be activated");
- var plugin = get_test_plugin();
+ var plugin = getTestPlugin();
plugin.disabled = false;
plugin.blocklisted = false;
Services.perms.removeAll();
Services.prefs.setBoolPref("plugins.click_to_play", true);
prepareTest(test15, gTestRoot + "plugin_alternate_content.html");
}
// Tests that the overlay is shown instead of alternate content when
@@ -635,17 +623,17 @@ function test18c() {
is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE, "Test 18c, plugin fallback type should be PLUGIN_VULNERABLE_NO_UPDATE");
ok(!objLoadingContent.activated, "Test 18c, Plugin should not be activated");
var overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
ok(overlay.style.visibility != "hidden", "Test 18c, Plugin overlay should exist, not be hidden");
var updateLink = doc.getAnonymousElementByAttribute(plugin, "class", "checkForUpdatesLink");
ok(updateLink.style.display != "block", "Test 18c, Plugin should not have an update link");
unregisterFakeBlocklistService();
- var plugin = get_test_plugin();
+ var plugin = getTestPlugin();
plugin.clicktoplay = false;
prepareTest(test19a, gTestRoot + "plugin_test.html");
}
// Tests that clicking the icon of the overlay activates the plugin
function test19a() {
var doc = gTestBrowser.contentDocument;
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_pluginplaypreview.js
@@ -0,0 +1,311 @@
+/* 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/. */
+
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir;
+
+var gTestBrowser = null;
+var gNextTest = null;
+var gNextTestSkip = 0;
+var gPlayPreviewPluginActualEvents = 0;
+var gPlayPreviewPluginExpectedEvents = 1;
+
+var gPlayPreviewRegistration = null;
+
+function registerPlayPreview(mimeType, targetUrl) {
+
+ function StreamConverterFactory() {}
+ StreamConverterFactory.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory]),
+ _targetConstructor: null,
+
+ register: function register(targetConstructor) {
+ this._targetConstructor = targetConstructor;
+ var proto = targetConstructor.prototype;
+ var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.registerFactory(proto.classID, proto.classDescription,
+ proto.contractID, this);
+ },
+
+ unregister: function unregister() {
+ var proto = this._targetConstructor.prototype;
+ var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.unregisterFactory(proto.classID, this);
+ this._targetConstructor = null;
+ },
+
+ // nsIFactory
+ createInstance: function createInstance(aOuter, iid) {
+ if (aOuter !== null)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ return (new (this._targetConstructor)).QueryInterface(iid);
+ },
+
+ // nsIFactory
+ lockFactory: function lockFactory(lock) {
+ // No longer used as of gecko 1.7.
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ }
+ };
+
+ function OverlayStreamConverter() {}
+ OverlayStreamConverter.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsISupports,
+ Ci.nsIStreamConverter,
+ Ci.nsIStreamListener,
+ Ci.nsIRequestObserver
+ ]),
+
+ classID: Components.ID('{4c6030f7-e20a-264f-0f9b-ada3a9e97384}'),
+ classDescription: 'overlay-test-data Component',
+ contractID: '@mozilla.org/streamconv;1?from=application/x-moz-playpreview&to=*/*',
+
+ // nsIStreamConverter::convert
+ convert: function(aFromStream, aFromType, aToType, aCtxt) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ // nsIStreamConverter::asyncConvertData
+ asyncConvertData: function(aFromType, aToType, aListener, aCtxt) {
+ var isValidRequest = false;
+ try {
+ var request = aCtxt;
+ request.QueryInterface(Ci.nsIChannel);
+ var spec = request.URI.spec;
+ var expectedSpec = 'data:application/x-moz-playpreview;,' + mimeType;
+ isValidRequest = (spec == expectedSpec);
+ } catch (e) { }
+ if (!isValidRequest)
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+
+ // Store the listener passed to us
+ this.listener = aListener;
+ },
+
+ // nsIStreamListener::onDataAvailable
+ onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
+ // Do nothing since all the data loading is handled by the viewer.
+ ok(false, "onDataAvailable should not be called");
+ },
+
+ // nsIRequestObserver::onStartRequest
+ onStartRequest: function(aRequest, aContext) {
+
+ // Setup the request so we can use it below.
+ aRequest.QueryInterface(Ci.nsIChannel);
+ // Cancel the request so the viewer can handle it.
+ aRequest.cancel(Cr.NS_BINDING_ABORTED);
+
+ // Create a new channel that is viewer loaded as a resource.
+ var ioService = Services.io;
+ var channel = ioService.newChannel(targetUrl, null, null);
+ channel.asyncOpen(this.listener, aContext);
+ },
+
+ // nsIRequestObserver::onStopRequest
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ // Do nothing.
+ }
+ };
+
+ var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ ph.registerPlayPreviewMimeType(mimeType);
+
+ var factory = new StreamConverterFactory();
+ factory.register(OverlayStreamConverter);
+
+ return (gPlayPreviewRegistration = {
+ unregister: function() {
+ ph.unregisterPlayPreviewMimeType(mimeType);
+ factory.unregister();
+ gPlayPreviewRegistration = null;
+ }
+ });
+}
+
+function unregisterPlayPreview() {
+ gPlayPreviewRegistration.unregister();
+}
+
+Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+
+function test() {
+ waitForExplicitFinish();
+ registerCleanupFunction(function() {
+ if (gPlayPreviewRegistration)
+ gPlayPreviewRegistration.unregister();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ });
+
+ var newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ gTestBrowser.addEventListener("load", pageLoad, true);
+ gTestBrowser.addEventListener("PluginPlayPreview", handlePluginPlayPreview, true);
+
+ registerPlayPreview('application/x-test', 'about:');
+ prepareTest(test1a, gTestRoot + "plugin_test.html", 1);
+}
+
+function finishTest() {
+ gTestBrowser.removeEventListener("load", pageLoad, true);
+ gTestBrowser.removeEventListener("PluginPlayPreview", handlePluginPlayPreview, true);
+ gBrowser.removeCurrentTab();
+ window.focus();
+ finish();
+}
+
+function handlePluginPlayPreview() {
+ gPlayPreviewPluginActualEvents++;
+}
+
+function pageLoad() {
+ // The plugin events are async dispatched and can come after the load event
+ // This just allows the events to fire before we then go on to test the states
+
+ // iframe might triggers load event as well, making sure we skip some to let
+ // all iframes on the page be loaded as well
+ if (gNextTestSkip) {
+ gNextTestSkip--;
+ return;
+ }
+ executeSoon(gNextTest);
+}
+
+function prepareTest(nextTest, url, skip) {
+ gNextTest = nextTest;
+ gNextTestSkip = skip;
+ gTestBrowser.contentWindow.location = url;
+}
+
+// Tests a page with normal play preview registration (1/2)
+function test1a() {
+ var notificationBox = gBrowser.getNotificationBox(gTestBrowser);
+ ok(!notificationBox.getNotificationWithValue("missing-plugins"), "Test 1a, Should not have displayed the missing plugin notification");
+ ok(!notificationBox.getNotificationWithValue("blocked-plugins"), "Test 1a, Should not have displayed the blocked plugin notification");
+
+ var pluginInfo = getTestPlugin();
+ ok(pluginInfo, "Should have a test plugin");
+
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW, "Test 1a, plugin fallback type should be PLUGIN_PLAY_PREVIEW");
+ ok(!objLoadingContent.activated, "Test 1a, Plugin should not be activated");
+
+ var overlay = doc.getAnonymousElementByAttribute(plugin, "class", "previewPluginContent");
+ ok(overlay, "Test 1a, the overlay div is expected");
+
+ var iframe = overlay.getElementsByClassName("previewPluginContentFrame")[0];
+ ok(iframe && iframe.localName == "iframe", "Test 1a, the overlay iframe is expected");
+ var iframeHref = iframe.contentWindow.location.href;
+ ok(iframeHref == "about:", "Test 1a, the overlay about: content is expected");
+
+ var rect = iframe.getBoundingClientRect();
+ ok(rect.width == 200, "Test 1a, Plugin with id=" + plugin.id + " overlay rect should have 200px width before being replaced by actual plugin");
+ ok(rect.height == 200, "Test 1a, Plugin with id=" + plugin.id + " overlay rect should have 200px height before being replaced by actual plugin");
+
+ var e = overlay.ownerDocument.createEvent("CustomEvent");
+ e.initCustomEvent("MozPlayPlugin", true, true, null);
+ overlay.dispatchEvent(e);
+ var condition = function() objLoadingContent.activated;
+ waitForCondition(condition, test1b, "Test 1a, Waited too long for plugin to stop play preview");
+}
+
+// Tests that activating via MozPlayPlugin through the notification works (part 2/2)
+function test1b() {
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 1b, Plugin should be activated");
+
+ is(gPlayPreviewPluginActualEvents, gPlayPreviewPluginExpectedEvents,
+ "There should be exactly one PluginPlayPreview event");
+
+ unregisterPlayPreview();
+
+ prepareTest(test2, gTestRoot + "plugin_test.html");
+}
+
+// Tests a page with a working plugin in it -- the mime type was just unregistered.
+function test2() {
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 2, Plugin should be activated");
+
+ registerPlayPreview('application/x-unknown', 'about:');
+
+ prepareTest(test3, gTestRoot + "plugin_test.html");
+}
+
+// Tests a page with a working plugin in it -- diffent play preview type is reserved.
+function test3() {
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 3, Plugin should be activated");
+
+ unregisterPlayPreview();
+
+ registerPlayPreview('application/x-test', 'about:');
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ prepareTest(test4a, gTestRoot + "plugin_test.html", 1);
+}
+
+// Test a fallback to the click-to-play
+function test4a() {
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW, "Test 4a, plugin fallback type should be PLUGIN_PLAY_PREVIEW");
+ ok(!objLoadingContent.activated, "Test 4a, Plugin should not be activated");
+
+ var overlay = doc.getAnonymousElementByAttribute(plugin, "class", "previewPluginContent");
+ ok(overlay, "Test 4a, the overlay div is expected");
+
+ var e = overlay.ownerDocument.createEvent("CustomEvent");
+ e.initCustomEvent("MozPlayPlugin", true, true, true);
+ overlay.dispatchEvent(e);
+ var condition = function() objLoadingContent.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY;
+ waitForCondition(condition, test4b, "Test 4a, Waited too long for plugin to stop play preview");
+}
+
+function test4b() {
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.pluginFallbackType != Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW, "Test 4b, plugin fallback type should not be PLUGIN_PLAY_PREVIEW");
+ ok(!objLoadingContent.activated, "Test 4b, Plugin should not be activated");
+
+ prepareTest(test5a, gTestRoot + "plugin_test.html", 1);
+}
+
+// Test a bypass of the click-to-play
+function test5a() {
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW, "Test 5a, plugin fallback type should be PLUGIN_PLAY_PREVIEW");
+ ok(!objLoadingContent.activated, "Test 5a, Plugin should not be activated");
+
+ var overlay = doc.getAnonymousElementByAttribute(plugin, "class", "previewPluginContent");
+ ok(overlay, "Test 5a, the overlay div is expected");
+
+ var e = overlay.ownerDocument.createEvent("CustomEvent");
+ e.initCustomEvent("MozPlayPlugin", true, true, false);
+ overlay.dispatchEvent(e);
+ var condition = function() objLoadingContent.activated;
+ waitForCondition(condition, test5b, "Test 5a, Waited too long for plugin to stop play preview");
+}
+
+function test5b() {
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 5b, Plugin should be activated");
+
+ finishTest();
+}
+
--- a/browser/base/content/test/browser_social_chatwindow.js
+++ b/browser/base/content/test/browser_social_chatwindow.js
@@ -50,17 +50,17 @@ var tests = {
break;
case "got-chatbox-message":
ok(true, "got chatbox message");
ok(e.data.result == "ok", "got chatbox windowRef result: "+e.data.result);
chats.selectedChat.toggle();
break;
}
}
- port.postMessage({topic: "test-init"});
+ port.postMessage({topic: "test-init", data: { id: 1 }});
},
testManyChats: function(next) {
// open enough chats to overflow the window, then check
// if the menupopup is visible
let port = Social.provider.port;
ok(port, "provider has a port");
let width = document.documentElement.boxObject.width;
let numToOpen = (width / 200) + 1;
@@ -82,13 +82,31 @@ var tests = {
}
ok(!chats.selectedChat, "chats are all closed");
next();
break;
}
}
let num = numToOpen;
while (num-- > 0) {
- port.postMessage({topic: "test-chatbox-open"});
+ port.postMessage({topic: "test-chatbox-open", data: { id: num }});
}
+ },
+ testWorkerChatWindow: function(next) {
+ let port = Social.provider.port;
+ ok(port, "provider has a port");
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "got-chatbox-message":
+ ok(true, "got a chat window opened");
+ let chats = document.getElementById("pinnedchats");
+ while (chats.selectedChat) {
+ chats.selectedChat.close();
+ }
+ ok(!chats.selectedChat, "chats are all closed");
+ next();
+ break;
+ }
+ }
+ port.postMessage({topic: "test-worker-chat" });
}
}
-
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_social_flyout.js
@@ -0,0 +1,48 @@
+/* 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/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ let manifest = { // normal provider
+ name: "provider 1",
+ origin: "https://example.com",
+ sidebarURL: "https://example.com/browser/browser/base/content/test/social_sidebar.html",
+ workerURL: "https://example.com/browser/browser/base/content/test/social_worker.js",
+ iconURL: "chrome://branding/content/icon48.png"
+ };
+ runSocialTestWithProvider(manifest, function (finishcb) {
+ runSocialTests(tests, undefined, undefined, finishcb);
+ });
+}
+
+var tests = {
+ testOpenCloseFlyout: function(next) {
+ let panel = document.getElementById("social-flyout-panel");
+ let port = Social.provider.port;
+ ok(port, "provider has a port");
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "got-sidebar-message":
+ port.postMessage({topic: "test-flyout-open"});
+ break;
+ case "got-flyout-visibility":
+ if (e.data.result == "hidden") {
+ ok(true, "flyout visibility is 'hidden'");
+ next();
+ } else if (e.data.result == "shown") {
+ ok(true, "flyout visibility is 'shown");
+ panel.hidePopup();
+ }
+ break;
+ case "got-flyout-message":
+ ok(e.data.result == "ok", "got flyout message");
+ break;
+ }
+ }
+ port.postMessage({topic: "test-init"});
+ }
+}
+
--- a/browser/base/content/test/browser_social_mozSocial_API.js
+++ b/browser/base/content/test/browser_social_mozSocial_API.js
@@ -37,26 +37,29 @@ var tests = {
let port = Social.provider.port;
ok(port, "provider has a port");
port.onmessage = function (e) {
let topic = e.data.topic;
switch (topic) {
case "got-panel-message":
ok(true, "got panel message");
+ // Check the panel isn't in our history.
+ ensureSocialUrlNotRemembered(e.data.location);
break;
case "got-social-panel-visibility":
if (e.data.result == "shown") {
ok(true, "panel shown");
let panel = document.getElementById("social-notification-panel");
panel.hidePopup();
} else if (e.data.result == "hidden") {
ok(true, "panel hidden");
next();
}
+ break;
case "got-sidebar-message":
// The sidebar message will always come first, since it loads by default
ok(true, "got sidebar message");
gotSidebarMessage = true;
checkNext();
break;
}
}
@@ -75,53 +78,10 @@ var tests = {
// Let the other observers (like the one that updates the UI) run before
// checking the icons.
executeSoon(function () {
iconsReady = true;
checkNext();
});
}, "social:ambient-notification-changed", false);
}
- },
-
- testServiceWindow: function(next) {
- // our test provider was initialized in the test above, we just
- // initiate our specific test now.
- let port = Social.provider.port;
- ok(port, "provider has a port");
- port.postMessage({topic: "test-service-window"});
- port.onmessage = function (e) {
- let topic = e.data.topic;
- switch (topic) {
- case "got-service-window-message":
- // The sidebar message will always come first, since it loads by default
- ok(true, "got service window message");
- port.postMessage({topic: "test-close-service-window"});
- break;
- case "got-service-window-closed-message":
- ok(true, "got service window closed message");
- next();
- break;
- }
- }
- },
-
- testServiceWindowTwice: function(next) {
- let port = Social.provider.port;
- port.postMessage({topic: "test-service-window-twice"});
- Social.provider.port.onmessage = function (e) {
- let topic = e.data.topic;
- switch (topic) {
- case "test-service-window-twice-result":
- is(e.data.result, "ok", "only one window should open when name is reused");
- break;
- case "got-service-window-message":
- ok(true, "got service window message");
- port.postMessage({topic: "test-close-service-window"});
- break;
- case "got-service-window-closed-message":
- ok(true, "got service window closed message");
- next();
- break;
- }
- }
}
}
--- a/browser/base/content/test/browser_social_shareButton.js
+++ b/browser/base/content/test/browser_social_shareButton.js
@@ -1,17 +1,13 @@
/* 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/. */
let prefName = "social.enabled",
- shareButton,
- sharePopup,
- okButton,
- undoButton,
gFinishCB;
function test() {
waitForExplicitFinish();
// Need to load a non-empty page for the social share button to appear
let tab = gBrowser.selectedTab = gBrowser.addTab("about:", {skipAnimation: true});
tab.linkedBrowser.addEventListener("load", function tabLoad(event) {
@@ -41,54 +37,80 @@ function tabLoaded() {
});
}
function testInitial(finishcb) {
ok(Social.provider, "Social provider is active");
ok(Social.provider.enabled, "Social provider is enabled");
ok(Social.provider.port, "Social provider has a port to its FrameWorker");
- shareButton = SocialShareButton.shareButton;
- sharePopup = SocialShareButton.sharePopup;
+ let {shareButton, sharePopup} = SocialShareButton;
ok(shareButton, "share button exists");
ok(sharePopup, "share popup exists");
- okButton = document.getElementById("editSharePopupOkButton");
- undoButton = document.getElementById("editSharePopupUndoButton");
-
- is(shareButton.hidden, false, "share button should be visible");
+ let okButton = document.getElementById("editSharePopupOkButton");
+ let undoButton = document.getElementById("editSharePopupUndoButton");
+ let shareStatusLabel = document.getElementById("share-button-status");
- // Test clicking the share button
- shareButton.addEventListener("click", function listener() {
- shareButton.removeEventListener("click", listener);
- is(shareButton.hasAttribute("shared"), true, "Share button should have 'shared' attribute after share button is clicked");
- executeSoon(testSecondClick.bind(window, testPopupOKButton));
- });
- EventUtils.synthesizeMouseAtCenter(shareButton, {});
+ // ensure the worker initialization and handshakes are all done and we
+ // have a profile and the worker has responsed to the recommend-prompt msg.
+ waitForCondition(function() Social.provider.profile && SocialShareButton.promptImages != null, function() {
+ is(shareButton.hasAttribute("shared"), false, "Share button should not have 'shared' attribute before share button is clicked");
+ // check dom values
+ let profile = Social.provider.profile;
+ let portrait = document.getElementById("socialUserPortrait").getAttribute("src");
+ is(profile.portrait, portrait, "portrait is set");
+ let displayName = document.getElementById("socialUserDisplayName");
+ is(displayName.label, profile.displayName, "display name is set");
+ ok(!document.getElementById("editSharePopupHeader").hidden, "user profile is visible");
+
+ // Check the strings from our worker actually ended up on the button.
+ is(shareButton.getAttribute("tooltiptext"), "Share this page", "check tooltip text is correct");
+ is(shareStatusLabel.getAttribute("value"), "", "check status label text is blank");
+ // Check the relative URL was resolved correctly (note this image has offsets of zero...)
+ is(shareButton.style.backgroundImage, 'url("https://example.com/browser/browser/base/content/test/social_share_image.png")', "check image url is correct");
+
+ // Test clicking the share button
+ shareButton.addEventListener("click", function listener() {
+ shareButton.removeEventListener("click", listener);
+ is(shareButton.hasAttribute("shared"), true, "Share button should have 'shared' attribute after share button is clicked");
+ is(shareButton.getAttribute("tooltiptext"), "Unshare this page", "check tooltip text is correct");
+ is(shareStatusLabel.getAttribute("value"), "This page has been shared", "check status label text is correct");
+ // Check the URL and offsets were applied correctly
+ is(shareButton.style.backgroundImage, 'url("https://example.com/browser/browser/base/content/test/social_share_image.png")', "check image url is correct");
+ executeSoon(testSecondClick.bind(window, testPopupOKButton));
+ });
+ EventUtils.synthesizeMouseAtCenter(shareButton, {});
+ }, "provider didn't provide user-recommend-prompt response");
}
function testSecondClick(nextTest) {
+ let {shareButton, sharePopup} = SocialShareButton;
sharePopup.addEventListener("popupshown", function listener() {
sharePopup.removeEventListener("popupshown", listener);
ok(true, "popup was shown after second click");
executeSoon(nextTest);
});
EventUtils.synthesizeMouseAtCenter(shareButton, {});
}
function testPopupOKButton() {
+ let {shareButton, sharePopup} = SocialShareButton;
+ let okButton = document.getElementById("editSharePopupOkButton");
sharePopup.addEventListener("popuphidden", function listener() {
sharePopup.removeEventListener("popuphidden", listener);
is(shareButton.hasAttribute("shared"), true, "Share button should still have 'shared' attribute after OK button is clicked");
executeSoon(testSecondClick.bind(window, testPopupUndoButton));
});
EventUtils.synthesizeMouseAtCenter(okButton, {});
}
function testPopupUndoButton() {
+ let {shareButton, sharePopup} = SocialShareButton;
+ let undoButton = document.getElementById("editSharePopupUndoButton");
sharePopup.addEventListener("popuphidden", function listener() {
sharePopup.removeEventListener("popuphidden", listener);
is(shareButton.hasAttribute("shared"), false, "Share button should not have 'shared' attribute after Undo button is clicked");
executeSoon(testShortcut);
});
EventUtils.synthesizeMouseAtCenter(undoButton, {});
}
@@ -97,28 +119,31 @@ function testShortcut() {
keyTarget.addEventListener("keyup", function listener() {
keyTarget.removeEventListener("keyup", listener);
executeSoon(checkShortcutWorked.bind(window, keyTarget));
});
EventUtils.synthesizeKey("l", {accelKey: true, shiftKey: true}, keyTarget);
}
function checkShortcutWorked(keyTarget) {
+ let {sharePopup, shareButton} = SocialShareButton;
is(shareButton.hasAttribute("shared"), true, "Share button should be in the 'shared' state after keyboard shortcut is used");
// Test a second invocation of the shortcut
sharePopup.addEventListener("popupshown", function listener() {
sharePopup.removeEventListener("popupshown", listener);
ok(true, "popup was shown after second use of keyboard shortcut");
executeSoon(checkOKButton);
});
EventUtils.synthesizeKey("l", {accelKey: true, shiftKey: true}, keyTarget);
}
function checkOKButton() {
+ let okButton = document.getElementById("editSharePopupOkButton");
+ let undoButton = document.getElementById("editSharePopupUndoButton");
is(document.activeElement, okButton, "ok button should be focused by default");
// This rest of particular test doesn't really apply on Mac, since buttons
// aren't focusable by default.
if (navigator.platform.indexOf("Mac") != -1) {
executeSoon(testCloseBySpace);
return;
}
@@ -151,22 +176,24 @@ function checkNextInTabOrder(element, ne
// Register a cleanup function to remove the listener in case this test fails
registerCleanupFunction(function () {
element.removeEventListener("focus", listener);
});
EventUtils.synthesizeKey("VK_TAB", {});
}
function testCloseBySpace() {
- is(document.activeElement.id, okButton.id, "testCloseBySpace, the ok button should be focused");
+ let sharePopup = SocialShareButton.sharePopup;
+ is(document.activeElement.id, "editSharePopupOkButton", "testCloseBySpace, the ok button should be focused");
sharePopup.addEventListener("popuphidden", function listener() {
sharePopup.removeEventListener("popuphidden", listener);
ok(true, "space closed the share popup");
executeSoon(testDisable);
});
EventUtils.synthesizeKey("VK_SPACE", {});
}
function testDisable() {
+ let shareButton = SocialShareButton.shareButton;
Services.prefs.setBoolPref(prefName, false);
is(shareButton.hidden, true, "Share button should be hidden when pref is disabled");
gFinishCB();
}
--- a/browser/base/content/test/head.js
+++ b/browser/base/content/test/head.js
@@ -83,31 +83,71 @@ function waitForCondition(condition, nex
if (condition()) {
moveOn();
}
tries++;
}, 100);
var moveOn = function() { clearInterval(interval); nextTest(); };
}
+// Check that a specified (string) URL hasn't been "remembered" (ie, is not
+// in history, will not appear in about:newtab or auto-complete, etc.)
+function ensureSocialUrlNotRemembered(url) {
+ let gh = Cc["@mozilla.org/browser/global-history;2"]
+ .getService(Ci.nsIGlobalHistory2);
+ let uri = Services.io.newURI(url, null, null);
+ ok(!gh.isVisited(uri), "social URL " + url + " should not be in global history");
+}
+
+function getTestPlugin() {
+ var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ var tags = ph.getPluginTags();
+
+ // Find the test plugin
+ for (var i = 0; i < tags.length; i++) {
+ if (tags[i].name == "Test Plug-in")
+ return tags[i];
+ }
+ ok(false, "Unable to find plugin");
+ return null;
+}
+
function runSocialTestWithProvider(manifest, callback) {
let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
+ // Check that none of the provider's content ends up in history.
+ registerCleanupFunction(function () {
+ for (let what of ['sidebarURL', 'workerURL', 'iconURL']) {
+ if (manifest[what]) {
+ ensureSocialUrlNotRemembered(manifest[what]);
+ }
+ }
+ });
+
info("runSocialTestWithProvider: " + manifest.toSource());
let oldProvider;
SocialService.addProvider(manifest, function(provider) {
info("runSocialTestWithProvider: provider added");
oldProvider = Social.provider;
Social.provider = provider;
// Now that we've set the UI's provider, enable the social functionality
Services.prefs.setBoolPref("social.enabled", true);
registerCleanupFunction(function () {
+ // if one test happens to fail, it is likely finishSocialTest will not
+ // be called, causing most future social tests to also fail as they
+ // attempt to add a provider which already exists - so work
+ // around that by also attempting to remove the test provider.
+ try {
+ SocialService.removeProvider(provider.origin, finish);
+ } catch (ex) {
+ ;
+ }
Social.provider = oldProvider;
Services.prefs.clearUserPref("social.enabled");
});
function finishSocialTest() {
SocialService.removeProvider(provider.origin, finish);
}
callback(finishSocialTest);
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/social_flyout.html
@@ -0,0 +1,22 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+ <script>
+ function pingWorker() {
+ var port = navigator.mozSocial.getWorker().port;
+ port.postMessage({topic: "flyout-message", result: "ok"});
+ }
+ window.addEventListener("socialFrameShow", function(e) {
+ var port = navigator.mozSocial.getWorker().port;
+ port.postMessage({topic: "flyout-visibility", result: "shown"});
+ }, false);
+ window.addEventListener("socialFrameHide", function(e) {
+ var port = navigator.mozSocial.getWorker().port;
+ port.postMessage({topic: "flyout-visibility", result: "hidden"});
+ }, false);
+ </script>
+ </head>
+ <body onload="pingWorker();">
+ <p>This is a test social flyout panel.</p>
+ </body>
+</html>
--- a/browser/base/content/test/social_panel.html
+++ b/browser/base/content/test/social_panel.html
@@ -1,15 +1,17 @@
<html>
<head>
<meta charset="utf-8">
<script>
function pingWorker() {
var port = navigator.mozSocial.getWorker().port;
- port.postMessage({topic: "panel-message", result: "ok"});
+ port.postMessage({topic: "panel-message",
+ result: "ok",
+ location: window.location.href});
}
window.addEventListener("socialFrameShow", function(e) {
var port = navigator.mozSocial.getWorker().port;
port.postMessage({topic: "status-panel-visibility", result: "shown"});
}, false);
window.addEventListener("socialFrameHide", function(e) {
var port = navigator.mozSocial.getWorker().port;
port.postMessage({topic: "status-panel-visibility", result: "hidden"});
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..fa1f8fb0e23b9689e51f667a5745898f929c0960
GIT binary patch
literal 934
zc$@*I16lluP)<h;3K|Lk000e1NJLTq001BW000mO0ssI2_+sh~00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGmbN~PnbOGLGA9w%&13O7XK~y+Tl~h?w
z6HyeMX{VqdxP&#JML>gzF^WngX*5D0u|Y_zMiP}w{Nn>rQGbz7P5f-4KNwn+O$AF>
zgVKtXvK0svEfujSv``QVEiLUV-kGsuAw}@@HhD91-#O>r^WH<~?G2J7H9f<d<x(^S
zLC^$25iY{@Soa9#T(SV!#&9@Y4ei=7lR026jnMF(LMVW<I4yTO@3fd(V9q6PO)zbp
zf7PN{b~tI8+Khm!V8A+XTU~4>%ov98nOhJH8ceDV?e4Hp5)gc%_{E?d0-#BRtRAg?
z*i-7p+yYcU5r3s@O5i1k=1!fqe<WgWxCj%vTyBb5R{`G{r`J)YE_?a)1r~&%6zX+)
zuO{bjQNf~prKa)ygn2qVEHouaJo&@IIp(jO?|V_}twFmU(7^}qs`rvuhM&Mu6y4N=
zZ&Nc>s*Z(4dvK5_OLD4PKMLBp0c*InFr8st)ta@owK*XwL`W7rL58Q|C<5?-2w=gd
z{t;DsmuJl*6rj0T68(sAb=AdM$cC@!OYWryP4}e=>9}pY;qu$un7rKRJk^y({3I?w
z1j>E|*T8kV$i3!y)1ua~dV%S}`Sj%#r*Xo}%jZ2X+Kh#Dg`@KZPALSQhC^_wD|#y`
zdKEr=-!`4K{ZiC5PfpEnaFWx<VxuD3H9CKEj-ca9hg>aB+>@BOKQlWayK11y-xy_&
zver<Gi7>tb6vTcuO%55xcLWE8hU^4$hQ~}d^K&swIAgU9jv9Yj=6HyEh1Yp+0RoO|
z$?KBT_|$?k1@K;}Q#Kiz9F&73$hN{Z3}sFO^FMOvAOwd3e6zL%pp1`=YVGVkm!2v~
zJN`kZ_osm_(~K}$go=vJ78N%XGkb<Y`o8y7;+12TvGR}Q{4Qb=H=IvsSJS&LX@=x^
zjS4==q#1{k#8cB&jZS|<Cgp4AOUZeb1M?6hM1D;kxhs-!g8hMxz*j|d<UVm+tbN4+
z+)3hiK!>~oaWQ}n6W&;Mu>c;!1Yw95DZN<AptFN}|3`Pg3v$il(e@2Jd8$}uY5sAE
zk~FC?YU=g%aF2o|zU|EHLv93U5yuP%%`$5{nv%Ssf&csX8$88<_@TT+ApigX07*qo
IM6N<$f?A)aGynhq
--- a/browser/base/content/test/social_sidebar.html
+++ b/browser/base/content/test/social_sidebar.html
@@ -1,45 +1,35 @@
<html>
<head>
<meta charset="utf-8">
<script>
- var win;
+ var testwindow;
function pingWorker() {
var port = navigator.mozSocial.getWorker().port;
port.onmessage = function(e) {
var topic = e.data.topic;
switch (topic) {
+ case "test-flyout-open":
+ navigator.mozSocial.openPanel("social_flyout.html");
+ break;
case "test-chatbox-open":
- navigator.mozSocial.openChatWindow("social_chat.html", function(chatwin) {
- port.postMessage({topic: "chatbox-opened", result: chatwin ? "ok" : "failed"});
+ var url = "social_chat.html";
+ var data = e.data.data;
+ if (data && data.id) {
+ url = url + "?id="+data.id;
+ }
+ navigator.mozSocial.openChatWindow(url, function(chatwin) {
+ port.postMessage({topic: "chatbox-opened",
+ result: chatwin ? "ok" : "failed"});
});
break;
- case "test-service-window":
- win = navigator.mozSocial.openServiceWindow("social_window.html", "test-service-window", "width=300,height=300");
- break;
- case "test-service-window-twice":
- win = navigator.mozSocial.openServiceWindow("social_window.html", "test-service-window", "width=300,height=300");
- var win2 = navigator.mozSocial.openServiceWindow("social_window.html", "test-service-window", "");
- var result;
- if (win == win2)
- result = "ok";
- else
- result = "not ok: " + win2 + " != " + win;
- port.postMessage({topic: "test-service-window-twice-result", result: result});
- break;
- case "test-close-service-window":
- win.addEventListener("unload", function watchClose() {
- win.removeEventListener("unload", watchClose);
- port.postMessage({topic: "service-window-closed-message", result: "ok"});
- }, false)
- win.close();
- break;
case "test-isVisible":
- port.postMessage({topic: "test-isVisible-response", result: navigator.mozSocial.isVisible});
+ port.postMessage({topic: "test-isVisible-response",
+ result: navigator.mozSocial.isVisible});
break;
}
}
port.postMessage({topic: "sidebar-message", result: "ok"});
}
</script>
</head>
<body onload="pingWorker();">
--- a/browser/base/content/test/social_window.html
+++ b/browser/base/content/test/social_window.html
@@ -1,14 +1,17 @@
<html>
<head>
<meta charset="utf-8">
<script>
function pingWorker() {
var port = navigator.mozSocial.getWorker().port;
- port.postMessage({topic: "service-window-message", result: "ok"});
+ port.postMessage({topic: "service-window-message",
+ location: window.location.href,
+ result: "ok"
+ });
}
</script>
</head>
<body onload="pingWorker();">
<p>This is a test social service window.</p>
</body>
</html>
--- a/browser/base/content/test/social_worker.js
+++ b/browser/base/content/test/social_worker.js
@@ -1,29 +1,30 @@
/* 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/. */
-let testPort, sidebarPort;
+let testPort, sidebarPort, apiPort;
onconnect = function(e) {
let port = e.ports[0];
port.onmessage = function onMessage(event) {
let topic = event.data.topic;
switch (topic) {
case "test-init":
testPort = port;
break;
case "sidebar-message":
sidebarPort = port;
if (testPort && event.data.result == "ok")
testPort.postMessage({topic:"got-sidebar-message"});
break;
case "service-window-message":
- testPort.postMessage({topic:"got-service-window-message"});
+ testPort.postMessage({topic:"got-service-window-message",
+ location: event.data.location});
break;
case "service-window-closed-message":
testPort.postMessage({topic:"got-service-window-closed-message"});
break;
case "test-service-window":
sidebarPort.postMessage({topic:"test-service-window"});
break;
case "test-service-window-twice":
@@ -32,46 +33,83 @@ onconnect = function(e) {
case "test-service-window-twice-result":
testPort.postMessage({topic: "test-service-window-twice-result", result: event.data.result })
break;
case "test-close-service-window":
sidebarPort.postMessage({topic:"test-close-service-window"});
break;
case "panel-message":
if (testPort && event.data.result == "ok")
- testPort.postMessage({topic:"got-panel-message"});
+ testPort.postMessage({topic:"got-panel-message",
+ location: event.data.location
+ });
break;
case "status-panel-visibility":
testPort.postMessage({topic:"got-social-panel-visibility", result: event.data.result });
break;
case "test-chatbox-open":
- sidebarPort.postMessage({topic:"test-chatbox-open"});
+ sidebarPort.postMessage( event.data );
break;
case "chatbox-message":
testPort.postMessage({topic:"got-chatbox-message", result: event.data.result});
break;
case "chatbox-visibility":
testPort.postMessage({topic:"got-chatbox-visibility", result: event.data.result});
break;
+ case "test-flyout-open":
+ sidebarPort.postMessage({topic:"test-flyout-open"});
+ break;
+ case "flyout-message":
+ testPort.postMessage({topic:"got-flyout-message", result: event.data.result});
+ break;
+ case "flyout-visibility":
+ testPort.postMessage({topic:"got-flyout-visibility", result: event.data.result});
+ break;
+ case "test-worker-chat":
+ apiPort.postMessage({topic: "social.request-chat", data: "https://example.com/browser/browser/base/content/test/social_chat.html" });
+ break;
case "social.initialize":
// This is the workerAPI port, respond and set up a notification icon.
+ apiPort = port;
port.postMessage({topic: "social.initialize-response"});
let profile = {
- userName: "foo"
+ portrait: "https://example.com/portrait.jpg",
+ userName: "trickster",
+ displayName: "Kuma Lisa",
+ profileURL: "http://en.wikipedia.org/wiki/Kuma_Lisa"
};
port.postMessage({topic: "social.user-profile", data: profile});
let icon = {
name: "testIcon",
iconURL: "chrome://branding/content/icon48.png",
contentPanel: "https://example.com/browser/browser/base/content/test/social_panel.html",
counter: 1
};
port.postMessage({topic: "social.ambient-notification", data: icon});
break;
case "test-isVisible":
sidebarPort.postMessage({topic: "test-isVisible"});
break;
case "test-isVisible-response":
testPort.postMessage({topic: "got-isVisible-response", result: event.data.result});
break;
+ case "social.user-recommend-prompt":
+ port.postMessage({
+ topic: "social.user-recommend-prompt-response",
+ data: {
+ images: {
+ // this one is relative to test we handle relative ones.
+ share: "browser/browser/base/content/test/social_share_image.png",
+ // absolute to check we handle them too.
+ unshare: "https://example.com/browser/browser/base/content/test/social_share_image.png"
+ },
+ messages: {
+ shareTooltip: "Share this page",
+ unshareTooltip: "Unshare this page",
+ sharedLabel: "This page has been shared",
+ unsharedLabel: "This page is no longer shared",
+ }
+ }
+ });
+ break;
}
}
}
--- a/browser/base/content/test/test_contextmenu.html
+++ b/browser/base/content/test/test_contextmenu.html
@@ -47,23 +47,23 @@ function openContextMenuFor(element, shi
function closeContextMenu() {
contextMenu.hidePopup();
}
function executeCopyCommand(command, expectedValue)
{
// Just execute the command directly rather than simulating a context menu
// press to avoid having to deal with its asynchronous nature
- subwindow.controllers.getControllerForCommand(command).doCommand(command);
+ SpecialPowers.wrap(subwindow).controllers.getControllerForCommand(command).doCommand(command);
// The easiest way to check the clipboard is to paste the contents into a
// textbox
input.focus();
input.value = "";
- input.controllers.getControllerForCommand("cmd_paste").doCommand("cmd_paste");
+ SpecialPowers.wrap(input).controllers.getControllerForCommand("cmd_paste").doCommand("cmd_paste");
is(input.value, expectedValue, "paste for command " + command);
}
function invokeItemAction(generatedItemId)
{
var item = contextMenu.getElementsByAttribute("generateditemid",
generatedItemId)[0];
ok(item, "Got generated XUL menu item");
@@ -245,17 +245,16 @@ function checkMenu(menu, expectedItems,
*
* Called by a popupshowing event handler. Each test checks for expected menu
* contents, closes the popup, and finally triggers the popup on a new element
* (thus kicking off another cycle).
*
*/
function runTest(testNum) {
// Seems we need to enable this again, or sendKeyEvent() complaints.
- netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
ok(true, "Starting test #" + testNum);
var inspectItems = [];
if (SpecialPowers.getBoolPref("devtools.inspector.enabled")) {
inspectItems = ["---", null,
"context-inspect", true];
}
--- a/browser/components/downloads/content/download.xml
+++ b/browser/components/downloads/content/download.xml
@@ -10,52 +10,41 @@
<bindings id="downloadBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="download"
extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
- <resources>
- <stylesheet src="chrome://browser/skin/downloads/downloads.css"/>
- </resources>
- <content orient="horizontal">
- <xul:hbox class="downloadInfo"
- align="center"
- flex="1"
- onclick="DownloadsView.onDownloadClick(event);">
- <xul:vbox pack="center">
- <xul:image class="downloadTypeIcon"
- validate="always"
- xbl:inherits="src=image"/>
- <xul:image class="downloadTypeIcon blockedIcon"/>
- </xul:vbox>
- <xul:vbox pack="center"
- flex="1">
- <xul:description class="downloadTarget"
- crop="center"
- xbl:inherits="value=target,tooltiptext=target"/>
- <xul:progressmeter anonid="progressmeter"
- class="downloadProgress"
- min="0"
- max="100"
- xbl:inherits="mode=progressmode,value=progress"/>
- <xul:description class="downloadDetails"
- crop="end"
- xbl:inherits="value=status,tooltiptext=statusTip"/>
- </xul:vbox>
- </xul:hbox>
- <xul:hbox class="downloadButtonContainer"
- align="center">
- <xul:button class="downloadButton downloadCancel"
- tooltiptext="&cmd.cancel.label;"
- oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_cancel');"/>
- <xul:button class="downloadButton downloadRetry"
- tooltiptext="&cmd.retry.label;"
- oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_retry');"/>
- <xul:button class="downloadButton downloadShow"
- tooltiptext="&cmd.show.label;"
- oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_show');"/>
- </xul:hbox>
+ <content orient="horizontal"
+ align="center"
+ onclick="DownloadsView.onDownloadClick(event);">
+ <xul:image class="downloadTypeIcon"
+ validate="always"
+ xbl:inherits="src=image"/>
+ <xul:image class="downloadTypeIcon blockedIcon"/>
+ <xul:vbox pack="center"
+ flex="1">
+ <xul:description class="downloadTarget"
+ crop="center"
+ xbl:inherits="value=target,tooltiptext=target"/>
+ <xul:progressmeter anonid="progressmeter"
+ class="downloadProgress"
+ min="0"
+ max="100"
+ xbl:inherits="mode=progressmode,value=progress"/>
+ <xul:description class="downloadDetails"
+ crop="end"
+ xbl:inherits="value=status,tooltiptext=statusTip"/>
+ </xul:vbox>
+ <xul:button class="downloadButton downloadCancel"
+ tooltiptext="&cmd.cancel.label;"
+ oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_cancel');"/>
+ <xul:button class="downloadButton downloadRetry"
+ tooltiptext="&cmd.retry.label;"
+ oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_retry');"/>
+ <xul:button class="downloadButton downloadShow"
+ tooltiptext="&cmd.show.label;"
+ oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_show');"/>
</content>
</binding>
</bindings>
--- a/browser/components/downloads/content/downloads.js
+++ b/browser/components/downloads/content/downloads.js
@@ -255,22 +255,21 @@ const DownloadsPanel = {
//////////////////////////////////////////////////////////////////////////////
//// Related operations
/**
* Shows or focuses the user interface dedicated to downloads history.
*/
showDownloadsHistory: function DP_showDownloadsHistory()
{
- // Hide the panel before invoking the Library window, otherwise focus will
- // return to the browser window when the panel closes automatically.
+ // Hide the panel before showing another window, otherwise focus will return
+ // to the browser window when the panel closes automatically.
this.hidePanel();
- // Open the Library window and select the Downloads query.
- PlacesCommandHook.showPlacesOrganizer("Downloads");
+ BrowserDownloadsUI();
},
//////////////////////////////////////////////////////////////////////////////
//// Internal functions
/**
* Move focus to the main element in the downloads panel, unless another
* element in the panel is already focused.
@@ -351,17 +350,17 @@ const DownloadsOverlayLoader = {
_loadedOverlays: {},
/**
* Loads the specified overlay and invokes the given callback when finished.
*
* @param aOverlay
* String containing the URI of the overlay to load in the current
* window. If this overlay has already been loaded using this
- * function, then the overlay is not loaded again.
+ * function, then the overlay is not loaded again.
* @param aCallback
* Invoked when loading is completed. If the overlay is already
* loaded, the function is called immediately.
*/
ensureOverlayLoaded: function DOL_ensureOverlayLoaded(aOverlay, aCallback)
{
// The overlay is already loaded, invoke the callback immediately.
if (aOverlay in this._loadedOverlays) {
@@ -420,47 +419,79 @@ const DownloadsOverlayLoader = {
* download state and real-time data. In addition, handles part of the user
* interaction events raised by the downloads list widget.
*/
const DownloadsView = {
//////////////////////////////////////////////////////////////////////////////
//// Functions handling download items in the list
/**
+ * Maximum number of items shown by the list at any given time.
+ */
+ kItemCountLimit: 3,
+
+ /**
* Indicates whether we are still loading downloads data asynchronously.
*/
loading: false,
/**
- * Object containing all the available DownloadsViewItem objects, indexed by
- * their numeric download identifier.
+ * Ordered array of all DownloadsDataItem objects. We need to keep this array
+ * because only a limited number of items are shown at once, and if an item
+ * that is currently visible is removed from the list, we might need to take
+ * another item from the array and make it appear at the bottom.
+ */
+ _dataItems: [],
+
+ /**
+ * Object containing the available DownloadsViewItem objects, indexed by their
+ * numeric download identifier. There is a limited number of view items in
+ * the panel at any given time.
*/
_viewItems: {},
/**
* Called when the number of items in the list changes.
*/
_itemCountChanged: function DV_itemCountChanged()
{
- if (Object.keys(this._viewItems).length > 0) {
+ let count = this._dataItems.length;
+ let hiddenCount = count - this.kItemCountLimit;
+
+ if (count > 0) {
DownloadsPanel.panel.setAttribute("hasdownloads", "true");
} else {
DownloadsPanel.panel.removeAttribute("hasdownloads");
}
+
+ let s = DownloadsCommon.strings;
+ this.downloadsHistory.label = (hiddenCount > 0)
+ ? s.showMoreDownloads(hiddenCount)
+ : s.showAllDownloads;
+ this.downloadsHistory.accessKey = s.showDownloadsAccessKey;
},
/**
* Element corresponding to the list of downloads.
*/
get richListBox()
{
delete this.richListBox;
return this.richListBox = document.getElementById("downloadsListBox");
},
+ /**
+ * Element corresponding to the button for showing more downloads.
+ */
+ get downloadsHistory()
+ {
+ delete this.downloadsHistory;
+ return this.downloadsHistory = document.getElementById("downloadsHistory");
+ },
+
//////////////////////////////////////////////////////////////////////////////
//// Callback functions from DownloadsData
/**
* Called before multiple downloads are about to be loaded.
*/
onDataLoadStarting: function DV_onDataLoadStarting()
{
@@ -469,16 +500,20 @@ const DownloadsView = {
/**
* Called after data loading finished.
*/
onDataLoadCompleted: function DV_onDataLoadCompleted()
{
this.loading = false;
+ // We suppressed item count change notifications during the batch load, at
+ // this point we should just call the function once.
+ this._itemCountChanged();
+
// Notify the panel that all the initially available downloads have been
// loaded. This ensures that the interface is visible, if still required.
DownloadsPanel.onViewLoadCompleted();
},
/**
* Called when the downloads database becomes unavailable (for example,
* entering Private Browsing Mode). References to existing data should be
@@ -488,16 +523,17 @@ const DownloadsView = {
{
DownloadsPanel.terminate();
// Clear the list by replacing with a shallow copy.
let emptyView = this.richListBox.cloneNode(false);
this.richListBox.parentNode.replaceChild(emptyView, this.richListBox);
this.richListBox = emptyView;
this._viewItems = {};
+ this._dataItems = [];
},
/**
* Called when a new download data item is available, either during the
* asynchronous data load or when a new download is started.
*
* @param aDataItem
* DownloadsDataItem object that was just added.
@@ -505,59 +541,119 @@ const DownloadsView = {
* When true, indicates that this item is the most recent and should be
* added in the topmost position. This happens when a new download is
* started. When false, indicates that the item is the least recent
* and should be appended. The latter generally happens during the
* asynchronous data load.
*/
onDataItemAdded: function DV_onDataItemAdded(aDataItem, aNewest)
{
- // Make the item and add it in the appropriate place in the list.
- let element = document.createElement("richlistitem");
- let viewItem = new DownloadsViewItem(aDataItem, element);
- this._viewItems[aDataItem.downloadId] = viewItem;
if (aNewest) {
- this.richListBox.insertBefore(element, this.richListBox.firstChild);
+ this._dataItems.unshift(aDataItem);
} else {
- this.richListBox.appendChild(element);
+ this._dataItems.push(aDataItem);
}
- this._itemCountChanged();
+ let itemsNowOverflow = this._dataItems.length > this.kItemCountLimit;
+ if (aNewest || !itemsNowOverflow) {
+ // The newly added item is visible in the panel and we must add the
+ // corresponding element. This is either because it is the first item, or
+ // because it was added at the bottom but the list still doesn't overflow.
+ this._addViewItem(aDataItem, aNewest);
+ }
+ if (aNewest && itemsNowOverflow) {
+ // If the list overflows, remove the last item from the panel to make room
+ // for the new one that we just added at the top.
+ this._removeViewItem(this._dataItems[this.kItemCountLimit]);
+ }
+
+ // For better performance during batch loads, don't update the count for
+ // every item, because the interface won't be visible until load finishes.
+ if (!this.loading) {
+ this._itemCountChanged();
+ }
},
/**
* Called when a data item is removed. Ensures that the widget associated
* with the view item is removed from the user interface.
*
* @param aDataItem
* DownloadsDataItem object that is being removed.
*/
onDataItemRemoved: function DV_onDataItemRemoved(aDataItem)
{
- let element = this.getViewItem(aDataItem)._element;
- let previousSelectedIndex = this.richListBox.selectedIndex;
- this.richListBox.removeChild(element);
- this.richListBox.selectedIndex = Math.min(previousSelectedIndex,
- this.richListBox.itemCount - 1);
- delete this._viewItems[aDataItem.downloadId];
+ let itemIndex = this._dataItems.indexOf(aDataItem);
+ this._dataItems.splice(itemIndex, 1);
+
+ if (itemIndex < this.kItemCountLimit) {
+ // The item to remove is visible in the panel.
+ this._removeViewItem(aDataItem);
+ if (this._dataItems.length >= this.kItemCountLimit) {
+ // Reinsert the next item into the panel.
+ this._addViewItem(this._dataItems[this.kItemCountLimit - 1], false);
+ }
+ }
this._itemCountChanged();
},
/**
* Returns the view item associated with the provided data item for this view.
*
* @param aDataItem
* DownloadsDataItem object for which the view item is requested.
*
* @return Object that can be used to notify item status events.
*/
getViewItem: function DV_getViewItem(aDataItem)
{
- return this._viewItems[aDataItem.downloadId];
+ // If the item is visible, just return it, otherwise return a mock object
+ // that doesn't react to notifications.
+ if (aDataItem.downloadId in this._viewItems) {
+ return this._viewItems[aDataItem.downloadId];
+ }
+ return this._invisibleViewItem;
+ },
+
+ /**
+ * Mock DownloadsDataItem object that doesn't react to notifications.
+ */
+ _invisibleViewItem: Object.freeze({
+ onStateChange: function () { },
+ onProgressChange: function () { }
+ }),
+
+ /**
+ * Creates a new view item associated with the specified data item, and adds
+ * it to the top or the bottom of the list.
+ */
+ _addViewItem: function DV_addViewItem(aDataItem, aNewest)
+ {
+ let element = document.createElement("richlistitem");
+ let viewItem = new DownloadsViewItem(aDataItem, element);
+ this._viewItems[aDataItem.downloadId] = viewItem;
+ if (aNewest) {
+ this.richListBox.insertBefore(element, this.richListBox.firstChild);
+ } else {
+ this.richListBox.appendChild(element);
+ }
+ },
+
+ /**
+ * Removes the view item associated with the specified data item.
+ */
+ _removeViewItem: function DV_removeViewItem(aDataItem)
+ {
+ let element = this.getViewItem(aDataItem)._element;
+ let previousSelectedIndex = this.richListBox.selectedIndex;
+ this.richListBox.removeChild(element);
+ this.richListBox.selectedIndex = Math.min(previousSelectedIndex,
+ this.richListBox.itemCount - 1);
+ delete this._viewItems[aDataItem.downloadId];
},
//////////////////////////////////////////////////////////////////////////////
//// User interface event functions
/**
* Helper function to do commands on a specific download item.
*
@@ -574,18 +670,19 @@ const DownloadsView = {
while (target.nodeName != "richlistitem") {
target = target.parentNode;
}
new DownloadsViewItemController(target).doCommand(aCommand);
},
onDownloadClick: function DV_onDownloadClick(aEvent)
{
- // Handle primary clicks only.
- if (aEvent.button == 0) {
+ // Handle primary clicks only, and exclude the action button.
+ if (aEvent.button == 0 &&
+ !aEvent.originalTarget.hasAttribute("oncommand")) {
goDoCommand("downloadsCmd_open");
}
},
onDownloadKeyPress: function DV_onDownloadKeyPress(aEvent)
{
// Handle unmodified keys only.
if (aEvent.altKey || aEvent.ctrlKey || aEvent.shiftKey || aEvent.metaKey) {
--- a/browser/components/downloads/content/downloadsOverlay.xul
+++ b/browser/components/downloads/content/downloadsOverlay.xul
@@ -103,14 +103,12 @@
flex="1"
context="downloadsContextMenu"
onkeypress="DownloadsView.onDownloadKeyPress(event);"
oncontextmenu="DownloadsView.onDownloadContextMenu(event);"
ondragstart="DownloadsView.onDownloadDragStart(event);"/>
<button id="downloadsHistory"
class="plain"
- label="&downloadshistory.label;"
- accesskey="&downloadshistory.accesskey;"
oncommand="DownloadsPanel.showDownloadsHistory();"/>
</panel>
</popupset>
</overlay>
--- a/browser/components/downloads/content/indicator.js
+++ b/browser/components/downloads/content/indicator.js
@@ -48,40 +48,16 @@ const DownloadsButton = {
* if not available because it has been removed from the toolbars.
*/
get _placeholder()
{
return document.getElementById("downloads-button");
},
/**
- * This function is called synchronously at window initialization. It only
- * sets the visibility of user interface elements to avoid flickering.
- *
- * NOTE: To keep startup time to a minimum, this function should not perform
- * any expensive operations or input/output, and should not cause the
- * Download Manager service to start.
- */
- initializePlaceholder: function DB_initializePlaceholder()
- {
- // Exit now if the feature is disabled. To improve startup time, we don't
- // load the DownloadsCommon module yet, but check the preference directly.
- if (gPrefService.getBoolPref("browser.download.useToolkitUI")) {
- return;
- }
-
- // We must hide the placeholder used for toolbar customization, unless it
- // has been removed from the toolbars and is now located in the palette.
- let placeholder = this._placeholder;
- if (placeholder) {
- placeholder.collapsed = true;
- }
- },
-
- /**
* This function is called asynchronously just after window initialization.
*
* NOTE: This function should limit the input/output it performs to improve
* startup time, and in particular should not cause the Download Manager
* service to start.
*/
initializeIndicator: function DB_initializeIndicator()
{
@@ -136,22 +112,18 @@ const DownloadsButton = {
*
* NOTE: This function is also called on startup, thus it should limit the
* input/output it performs, and in particular should not cause the
* Download Manager service to start.
*/
_update: function DB_update() {
this._updatePositionInternal();
- let placeholder = this._placeholder;
if (!DownloadsCommon.useToolkitUI) {
DownloadsIndicatorView.ensureInitialized();
- if (placeholder) {
- placeholder.collapsed = true;
- }
} else {
DownloadsIndicatorView.ensureTerminated();
}
},
/**
* Determines the position where the indicator should appear, and moves its
* associated element to the new position. This does not happen if the
@@ -176,57 +148,49 @@ const DownloadsButton = {
{
let indicator = DownloadsIndicatorView.indicator;
if (!indicator) {
// Exit now if the indicator overlay isn't loaded yet.
return null;
}
let placeholder = this._placeholder;
-
- // Firstly, determine if we should always hide the indicator.
- if (!placeholder && !this._anchorRequested &&
- !DownloadsIndicatorView.hasDownloads) {
+ if (!placeholder) {
+ // The placeholder has been removed from the browser window.
indicator.collapsed = true;
return null;
}
+
+ // Position the indicator where the placeholder is located. We should
+ // update the position even if the placeholder is located on an invisible
+ // toolbar, because the toolbar may be displayed later.
+ placeholder.parentNode.insertBefore(indicator, placeholder);
+ placeholder.collapsed = true;
indicator.collapsed = false;
indicator.open = this._anchorRequested;
- // Determine if we should display the indicator in a known position.
- if (placeholder) {
- placeholder.parentNode.insertBefore(indicator, placeholder);
- // Determine if the placeholder is located on a visible toolbar.
- if (isElementVisible(placeholder.parentNode)) {
- return DownloadsIndicatorView.indicatorAnchor;
- }
- }
-
- // If not customized, the indicator is normally in the navigation bar.
- // Always place it in the default position, unless we need an anchor.
- if (!this._anchorRequested) {
- this._navBar.appendChild(indicator);
+ // Determine if the placeholder is located on an invisible toolbar.
+ if (!isElementVisible(placeholder.parentNode)) {
return null;
}
- // Show the indicator temporarily in the navigation bar, if visible.
- if (isElementVisible(this._navBar)) {
- this._navBar.appendChild(indicator);
- return DownloadsIndicatorView.indicatorAnchor;
- }
+ return DownloadsIndicatorView.indicatorAnchor;
+ },
- // Show the indicator temporarily in the tab bar, if visible.
- if (!this._tabsToolbar.collapsed) {
- this._tabsToolbar.appendChild(indicator);
- return DownloadsIndicatorView.indicatorAnchor;
+ /**
+ * Indicates whether the indicator is visible in the browser window.
+ */
+ get isVisible()
+ {
+ if (!this._placeholder) {
+ return false;
}
-
- // The temporary anchor cannot be shown.
- return null;
+ let element = DownloadsIndicatorView.indicator || this._placeholder;
+ return isElementVisible(element.parentNode);
},
/**
* Indicates whether we should try and show the indicator temporarily as an
* anchor for the panel, even if the indicator would be hidden by default.
*/
_anchorRequested: false,
@@ -379,16 +343,20 @@ const DownloadsIndicatorView = {
return;
}
function DIV_SEN_callback() {
if (this._notificationTimeout) {
clearTimeout(this._notificationTimeout);
}
+ // Now that the overlay is loaded, place the indicator in its final
+ // position.
+ DownloadsButton.updatePosition();
+
let indicator = this.indicator;
indicator.setAttribute("notification", "true");
this._notificationTimeout = setTimeout(
function () indicator.removeAttribute("notification"), 1000);
}
this._ensureOperational(DIV_SEN_callback.bind(this));
},
@@ -525,17 +493,17 @@ const DownloadsIndicatorView = {
onCommand: function DIV_onCommand(aEvent)
{
if (DownloadsCommon.useToolkitUI) {
// The panel won't suppress attention for us, we need to clear now.
DownloadsCommon.indicatorData.attention = false;
}
- BrowserDownloadsUI();
+ DownloadsPanel.showPanel();
aEvent.stopPropagation();
},
onDragOver: function DIV_onDragOver(aEvent)
{
browserDragAndDrop.dragOver(aEvent);
},
--- a/browser/components/downloads/src/DownloadsCommon.jsm
+++ b/browser/components/downloads/src/DownloadsCommon.jsm
@@ -47,16 +47,18 @@ const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gBrowserGlue",
"@mozilla.org/browser/browserglue;1",
"nsIBrowserGlue");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
const nsIDM = Ci.nsIDownloadManager;
const kDownloadsStringBundleUrl =
"chrome://browser/locale/downloads/downloads.properties";
const kDownloadsStringsRequiringFormatting = {
sizeWithUnits: true,
@@ -64,16 +66,20 @@ const kDownloadsStringsRequiringFormatti
shortTimeLeftMinutes: true,
shortTimeLeftHours: true,
shortTimeLeftDays: true,
statusSeparator: true,
statusSeparatorBeforeNumber: true,
fileExecutableSecurityWarning: true
};
+const kDownloadsStringsRequiringPluralForm = {
+ showMoreDownloads: true
+};
+
XPCOMUtils.defineLazyGetter(this, "DownloadsLocalFileCtor", function () {
return Components.Constructor("@mozilla.org/file/local;1",
"nsILocalFile", "initWithPath");
});
////////////////////////////////////////////////////////////////////////////////
//// DownloadsCommon
@@ -97,16 +103,24 @@ const DownloadsCommon = {
let stringName = string.key;
if (stringName in kDownloadsStringsRequiringFormatting) {
strings[stringName] = function () {
// Convert "arguments" to a real array before calling into XPCOM.
return sb.formatStringFromName(stringName,
Array.slice(arguments, 0),
arguments.length);
};
+ } else if (stringName in kDownloadsStringsRequiringPluralForm) {
+ strings[stringName] = function (aCount) {
+ // Convert "arguments" to a real array before calling into XPCOM.
+ let formattedString = sb.formatStringFromName(stringName,
+ Array.slice(arguments, 0),
+ arguments.length);
+ return PluralForm.get(aCount, formattedString);
+ };
} else {
strings[stringName] = string.value;
}
}
delete this.strings;
return this.strings = strings;
},
@@ -430,33 +444,36 @@ const DownloadsData = {
if (aActiveOnly) {
if (this._loadState == this.kLoadNone) {
// Indicate to the views that a batch loading operation is in progress.
this._views.forEach(
function (view) view.onDataLoadStarting()
);
- // Reload the list using the Download Manager service.
+ // Reload the list using the Download Manager service. The list is
+ // returned in no particular order.
let downloads = Services.downloads.activeDownloads;
while (downloads.hasMoreElements()) {
let download = downloads.getNext().QueryInterface(Ci.nsIDownload);
this._getOrAddDataItem(download, true);
}
this._loadState = this.kLoadActive;
// Indicate to the views that the batch loading operation is complete.
this._views.forEach(
function (view) view.onDataLoadCompleted()
);
}
} else {
if (this._loadState != this.kLoadAll) {
// Load only the relevant columns from the downloads database. The
- // columns are read in the init_FromDataRow method of DownloadsDataItem.
+ // columns are read in the _initFromDataRow method of DownloadsDataItem.
+ // Order by descending download identifier so that the most recent
+ // downloads are notified first to the listening views.
let statement = Services.downloads.DBConnection.createAsyncStatement(
"SELECT id, target, name, source, referrer, state, "
+ "startTime, endTime, currBytes, maxBytes "
+ "FROM moz_downloads "
+ "ORDER BY id DESC"
);
try {
this._pendingStatement = statement.executeAsync(this);
--- a/browser/components/downloads/src/DownloadsUI.js
+++ b/browser/components/downloads/src/DownloadsUI.js
@@ -63,62 +63,32 @@ DownloadsUI.prototype = {
return;
}
if (!aReason) {
aReason = Ci.nsIDownloadManagerUI.REASON_USER_INTERACTED;
}
if (aReason == Ci.nsIDownloadManagerUI.REASON_NEW_DOWNLOAD) {
- // New download notifications are already handled by the panel service.
- // We don't handle them here because we don't want them to depend on the
- // "browser.download.manager.showWhenStarting" and
- // "browser.download.manager.focusWhenStarting" preferences.
- return;
+ // If the indicator is visible, then new download notifications are
+ // already handled by the panel service.
+ let browserWin = gBrowserGlue.getMostRecentBrowserWindow();
+ if (browserWin &&
+ browserWin.windowState != Ci.nsIDOMChromeWindow.STATE_MINIMIZED &&
+ browserWin.DownloadsButton.isVisible) {
+ return;
+ }
}
- // Show the panel in the most recent browser window, if present.
- let browserWin = gBrowserGlue.getMostRecentBrowserWindow();
- if (browserWin) {
- // The most recent browser window could have been minimized, in that case
- // it must be restored to allow the panel to open properly.
- if (browserWin.windowState == Ci.nsIDOMChromeWindow.STATE_MINIMIZED) {
- browserWin.restore();
- }
- browserWin.focus();
- browserWin.DownloadsPanel.showPanel();
- return;
- }
-
- // If no browser window is visible and the user requested to show the
- // current downloads, try and open a new window. We'll open the panel when
- // delayed loading is finished.
- Services.obs.addObserver(function DUIO_observe(aSubject, aTopic, aData) {
- Services.obs.removeObserver(DUIO_observe, aTopic);
- aSubject.DownloadsPanel.showPanel();
- }, "browser-delayed-startup-finished", false);
-
- // We must really build an empty arguments list for the new window.
- let windowFirstArg = Cc["@mozilla.org/supports-string;1"]
- .createInstance(Ci.nsISupportsString);
- let windowArgs = Cc["@mozilla.org/supports-array;1"]
- .createInstance(Ci.nsISupportsArray);
- windowArgs.AppendElement(windowFirstArg);
- Services.ww.openWindow(null, "chrome://browser/content/browser.xul",
- null, "chrome,dialog=no,all", windowArgs);
+ this._toolkitUI.show(aWindowContext, aID, aReason);
},
get visible()
{
- if (DownloadsCommon.useToolkitUI) {
- return this._toolkitUI.visible;
- }
-
- let browserWin = gBrowserGlue.getMostRecentBrowserWindow();
- return browserWin ? browserWin.DownloadsPanel.isPanelShowing : false;
+ return this._toolkitUI.visible;
},
getAttention: function DUI_getAttention()
{
if (DownloadsCommon.useToolkitUI) {
this._toolkitUI.getAttention();
}
}