author | David Anderson <danderson@mozilla.com> |
Wed, 29 Aug 2012 17:51:24 -0700 | |
changeset 113609 | 6cd206b371761294125cb98fe9d0c11e2383795f |
parent 113608 | b63bb39ed1c08605128c984987bbf176dfd81999 (current diff) |
parent 110512 | a0240c1043eefc60f08dc3e4dd2e8a0043b0d75e (diff) |
child 113610 | 7bf95bb092331b1db96ba9d561400fcdfb9f09d6 |
push id | 239 |
push user | akeybl@mozilla.com |
push date | Thu, 03 Jan 2013 21:54:43 +0000 |
treeherder | mozilla-release@3a7b66445659 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 18.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- 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 0000000000000000000000000000000000000000..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 "