Les modes de compatibilités d'Internet Explorer 8

IE8 intègre à présent un système de Mode permettant les évolutions futures des produits IE de Microsoft. Ces modes permettent de changer le comportement (et donc les fonctionnalités) du navigateur pour qu’il puisse afficher au mieux les pages des sites qui ne seraient pas prévus à l’origine pour IE8.

Tableau récapitulatif des modes supportés

IE5 Affiche le contenu comme le ferait IE7 en « Quirks Mode ». Ce mode est très similaire au fonctionnement de IE5 pour afficher les contenus. Utilisation du moteur JavaScript présent dans IE7 (JScript Engine Version 5.7 Quirks Mode).
IE7 Affiche le contenu comme le ferait IE7 en « Standard mode », que la page ait ou non une directive valide. Utilisation du moteur JavaScript présent dans IE7 (JScript Engine Version 5.7). Ce mode ne passe pas du tout l’Acid test 2 et arrive péniblement à 12% de l’Acid test 3.
IE8 Mode de fonctionnement proposant le plus haut support des standards sur Internet Explorer. W3C CSS 2.1, W3C Selectors API ainsi qu’un support limité du W3C CSS 3 (Working Draft). Utilisation du moteur JavaScript présent dans IE8 (JScript Engine Version 5.8). Ce mode passe l’Acid test 2 et a 20% à l’Acid test 3.
EmulateIE7      Affiche le contenu en utilisant le même principe que IE7 pour déterminer le choix de rendu entre le « Quirks Mode » et le « Standars Mode ». Suivant la directive présente dans le document, la page sera donc affichée à l’aide du mode IE5 (Quirks Mode) ou du mode IE7 (Standars Mode). Utilisation du moteur JavaScript présent dans IE7 (JScript Engine Version 5.7 ou 5.7 Quirks mode). Ce mode est le mode de compatibilité à préférer pour une très grande partie des sites.
EmulateIE8 Mode similaire à EmulateIE7. Internet Explorer utilise la directive présente dans le document pour choisir le mode de rendu entre le « Quirks Mode » et le « Standard Mode ». La page sera donc affichée à l’aide du mode IE5 (Quirks Mode) ou du mode IE8 (Standars Mode). Utilisation du moteur JavaScript présent dans IE8 (JScript Engine Version 5.8) ou du moteur JavaScript présent dans IE7 en Quirks Mode( JScript Engine Version 5.7 Quirks Mode).
Edge Mode de fonctionnement demandant d’utiliser le mode de compatibilité le plus haut possible. Si une nouvelle version de IE propose un nouveau mode, les pages en mode Edge utiliseront le nouveau mode du navigateur. Ce mode est déconseillé et n’est à utiliser que sur les environnements de test.

Attention ! Modes sauvages en liberté !

Par défaut, Internet Explorer utilise le mode EmulateIE8 pour afficher les pages qui viennent de la zone internet (« Internet Zone »). Par contre, les pages venant de la zone intranet (« Intranet Zone ») ou des contrôles WebBrowser utilisent le mode EmulateIE7 par défaut (par exemple, http://localhost/ vient de la zone intranet). Ces paramètres peuvent être changés.

Références :

Pour comparer avec les autres navigateurs (Acid test 3) :

Microsoft a mis en place un système de certification des sites pour le passage à IE8. Ce système permet au navigateur de savoir si le site sur lequel il va allé est validé pour IE8 ou pas et donc s’il faut proposer à l’utilisateur la possibilité de passé en mode compatibilité ou non. Les explications par rapport à ce système sont expliquées dans le lien suivant :

Le mode a un impact sur la chaine de caractère du « User Agent » de IE8 ainsi que sur les « Version Vector » qui servent aux codes conditionnels dans le JavaScript, dans les CSS ou dans les commentaires HTML. Normalement, ce changement n’a que peu d’impact sur un site web, cependant il est à prendre en compte.

Références :

Les arguments d’un évènement

Suite à une remarque d’Antoine, je me sens obligé aujourd’hui d’expliquer un point de conception lié à la modélisation des évènements dans CK.

Néanmoins, avant de plonger dans la technique, une remarque préliminaire s'impose:

L'Académie française, dans la neuvième édition de son Dictionnaire, écrit, en accord avec les recommandations du Conseil supérieur de la langue française de 1990, évènement. La graphie ancienne événement n'est cependant pas considérée comme fautive, encore que rien ne la justifie plus. Sa survivance s'explique par le fait que la régularisation de ce mot, ainsi que de quelques autres, d'abrègement à vènerie, avait été oubliée lors de la préparation tant de la septième édition (1878) que de la huitième (1935).
www.academie-francaise.fr/langue/rectifications_1990.pdf

Hériter… ou pas.

En règle générale, les concepteurs essayent d’éviter autant que possible les arbres d’héritage profonds : une structure d’héritage « flat » (et orthogonale) est toujours préférable (à une horreur de type MFC) car elle garantit à la fois une meilleure lisibilité (et donc maintenabilité) et réutilisabilité. Pourtant, dans CK, il existe un endroit où la structure d’héritage n’est pas du tout, mais alors vraiment pas, plate… Il s’agit des objets arguments d’évènements. Par exemple, l’argument de l’évènement correspondant à l’échange des modes de deux touches réelles. Cette classe s’appelle ActualKeyModeSwappedEventArgs et voici sa chaîne d’héritage:

ActualKeyModeSwappedEventArgs Les modes de deux touches ont été échangés.
 > ActualKeyModeChangedEventArgs  Le mode d’une touche a changé.
  > ActualKeyEventArgs L’évènement concerne une touche réelle.
   > KeyEventArgs L’évènement concerne une touche.
    > KeyboardEventArgs L’évènement concerne un clavier.
     > ContextEventArgs L’évènement concerne un contexte CK.
      > EventArgs Un évènement s’est produit.

Six classes de base !

Pourquoi les arguments d’évènements s’inscrivent-ils dans une chaîne d’héritage si profonde ? Le concepteur aurait-il abusé de substances illicites ? Que nenni (enfin si mais cela n’a pas eu d’impact sur ce qui nous occupe). Nous avons là une exception qui confirme la règle de la platitude (je ne sais pas si c’est la seule exception), car cette chaîne d’héritage permet aux évènements d’être interceptés à n’importe quel niveau de précision. Et c’est bien pratique.

Le bon héritage

Relevons d’abord un aspect important : cette conception respecte totalement la sémantique de la spécialisation et ne crée aucun des problèmes souvent rencontrés lorsque l’on abuse de l’héritage.

  • Sur le respect de la sémantique d’abord, analysons la chaîne présentée ci-dessus :
    Le fait d’échanger les modes de deux touches est un (is_a) changement de mode qui est un (is_a) évènement d’une touche réelle qui est un (is_a) évènement d’une touche qui est un (is_a) évènement d’un clavier qui est un (is_a) évènement d’un contexte qui est un (is_a) évènement.
    Ce respect de la règle est un (is a en anglais) est la règle fondamentale à respecter lorsque l’on spécialise une classe. Si on ne peut pas dire A est un B, alors A ne devrait pas hériter de B (les exceptions à cette règle existent - mixins et autres héritages de réutilisation – mais ne les commettez qu’en toute connaissance de causes et d’effets).
  • Sur la mise en œuvre d’autre part, il faut noter que ces « classes évènements » sont implémentées sans mettre en œuvre de propriétés ou méthodes protégées : ici, l’héritage ne peut « casser l’encapsulation » (chercher dans votre moteur préféré « inheritance breaks encapsulation » pour plus d’information sur ce sujet parfois un peu subtil). Chaque classe apporte des propriétés publiques qui décrivent, précisent, l’évènement correspondant : aucune réutilisation d’implémentation n’est faite ici, on est dans le cas d’un sous-typage (subtyping) plutôt que d’un héritage (inheritance).

Cet usage de l’héritage est donc correct en regard des canons de l’ingénierie logicielle. Mais à quoi cela sert ?

« Client side » : je consomme l’évènement

Comme je l’ai déjà écrit ci-dessus, l’intérêt est de faciliter la prise en compte de ces évènements au « niveau de détail » que l’on souhaite. Considérons l’évènement correspondant à un des arguments ci-dessus :

public interface IKey
{
   ...
   event EventHandler ActualKeyModeChanged;
   ...
}

En tant que client, je peux inscrire ces évènements auprès de n’importe quelle fonction dont la signature respecte le type de l’argument en tenant compte de l’héritage. Je peux, bien évidemment, m’inscrire ainsi à l’évènement :

   IKey k = ...;
   k.ActualKeyModeChanged += OnKeyModeChanged;
   ...

void OnKeyModeChanged( object source, ActualKeyModeChangedEventArgs e )
{
   ...
}

Mais, je peux aussi m’intéresser à ce même évènement ainsi :

   IKey k = ...;
   ...
   k.ActualKeyModeChanged += OnSomethingChangedInKey;
   k.KeyPropertyChanged += OnSomethingChangedInKey;
   ...

void OnSomethingChangedInKey( object source, KeyEventArgs e )
{
   ...
}

Ou même :

   IKey k = ...;
   ...
   k.ActualKeyModeChanged += OnSomethingHappened;
   k.Keyboard.AvailableModeChanged += OnSomethingHappened;
   k.Context.CurrentKeyboardChanged += OnSomethingHappened;
   ...

void OnSomethingHappened( object source, EventArgs e )
{
   ...
}

Vous voyez l’idée : comme le changement de mode d’une touche réelle est un (is_a) évènement, je peux y souscrire justement comme à un évènement quelconque, je ne suis pas contraint d’utiliser le type précis de l’argument.

« Server Side » : j’émets un évènement

Il existe une autre utilisation possible de cet héritage si l’on se place de l’autre coté de la barrière : celui où on émet l’évènement plutôt que de le recevoir. En l’occurrence, il se trouve que ActualKeyModeSwappedEventArgs et ActualKeyModeChangedEventArgs illustrent cette utilisation.

Le premier correspond à l’échange des modes de deux touches, le deuxième (qui généralise le premier) à un changement de mode d’une touche.
Les interfaces IKey et IKeyboard exposent toutes deux l’évènement ActualKeyModeChanged. Le fait que le changement soit dû à un échange plutôt qu’à un changement direct est un détail pour la grande majorité des clients (les plugins en l’occurrence), mais on souhaite quand même exposer la cause de l’évènement à ceux qui sont susceptibles de s’y intéresser. La première idée est d’exposer un deuxième évènement :

public interface IKey
{
   ...
   event EventHandler<ActualKeyModeChangedEventArgs> ActualKeyModeChanged;
   event EventHandler<ActualKeyModeSwappedEventArgs> ActualKeyModeSwapped;
   ...
}

Mais cela pose un vrai problème de conception : tout déclenchement de l’évènement Swapped doit être doublé d’un déclenchement de Changed sinon un client qui ne s’est inscrit qu’au premier ne verra jamais les changements causés par un échange de mode. Outre la pollution de l’interface IKey par un évènement peu utilisé, cela entraîne aussi le fait que le client qui s’intéresse aux deux devra dédoublonner les évènements pour éviter de les prendre en compte deux fois… Ce ne sera pas fait. Ou mal fait. C’est ce qu’on appelle une usine à bugs.

La bonne solution consiste à utiliser l’héritage dans l’autre sens. On ne publie qu’un seul évènement :

public interface IKey
{
   ...
   /// 
   /// Fires whenever the mode of one of our <see cref="ActualKeys"/> changed.
   /// The event argument may be an instance of the 
   /// <see cref="ActualKeyModeSwappedEventArgs"/> class if the change 
   /// is the result of a call to <see cref="IActualKey.SwapModes"/> 
   /// instead of <see cref="IActualKey.ChangeMode"/>.
   /// 
   event EventHandler<ActualKeyModeChangedEventArgs> ActualKeyModeChanged;
   ...
}

Et son argument sera du type ActualKeyModeSwappedEventArgs dans le cas du Swap, ce qui est facile à tester par les clients que cela intéresse :

void OnKeyModeChanged( object source, ActualKeyModeChangedEventArgs e )
{
   ActualKeyModeSwappedEventArgs eSwap = e as ActualKeyModeSwappedEventArgs;
   if( eSwap != null )
   {
      // The change is due to a swap. The event argument gives us the swapped key.
      IActualKey other = eSwap.SwappedKey;
      ...
   }
   else
   {
     // Handle ChangeMode event.
     ...
   }
}

En résumé, cette technique permet de ne pas polluer les interfaces avec des évènements rarement utilisés et de permettre une prise en compte simple et efficace de « sous-catégories » précises d’évènements.

Conclusion

Le modèle des évènements de .Net, qui repose sur les délégués (delegates) et la signature standard (object source, EventArgs e) est simple et puissant. A condition de l’exploiter correctement, et donc de l’avoir compris.

Cette discussion, qui nous a amené à traiter du bon usage de l’héritage, introduit deux aspects de l’utilisation de l’héritage en conception. Pour aller plus loin, je vous encourage à regarder du coté de la co-variance et de la contra-variance qui sont des notions très proches de ce que nous avons présenté ici.

Dans un prochain post traitant des évènements, j’aborderai de ce que je considère comme une des (rares) erreurs de conception du framework .Net : la classe EventArgs elle-même…

NextResult() and so what ?

J’ai parfois du mal à me souvenir du fonctionnement précis de certaines API. Le comportement du IDataReader avec plusieurs résultats en fait partie. Un petit test unitaire s’impose :

[Test]
public void TestDataReaderBehavior()
{
       ExecuteReader( "select 1;",
              delegate( IDataReader r )
       {
             Assert.IsTrue( r.Read(), "We are directly BEFORE the first record." );
             Assert.AreEqual( 1, r.GetInt32( 0 ) );
             Assert.IsFalse( r.Read() );
             Assert.IsFalse( r.Read(), "One can always call Read." );
             Assert.IsFalse( r.NextResult() );
             Assert.IsFalse( r.NextResult(), "One can always call NextResult." );
             Assert.IsFalse( r.IsClosed, "A data reader must explicitely be closed (except if obtained with ExecuteReader( CommandBehavior.CloseConnection )." );
       } );
 
       ExecuteReader( "select 1; select 2;",
              delegate( IDataReader r )
       {
             Assert.IsTrue( r.Read() );
             Assert.AreEqual( 1, r.GetInt32( 0 ) );
             Assert.IsTrue( r.NextResult() );
             Assert.IsTrue( r.Read(), "Here also we are BEFORE the first record." );
             Assert.AreEqual( 2, r.GetInt32( 0 ) );
             Assert.IsFalse( r.Read() );
             Assert.IsFalse( r.NextResult() );
       } );
 
       ExecuteReader( "select 1 from sys.tables where 0=1; select 2;", 
              delegate( IDataReader r )
       {
             Assert.IsFalse( r.Read(), "First select has no results." );
             Assert.IsTrue( r.NextResult() );
             Assert.IsTrue( r.Read() );
             Assert.AreEqual( 2, r.GetInt32( 0 ) );
             Assert.IsFalse( r.Read() );
             Assert.IsFalse( r.NextResult() );
       } );
 
       ExecuteReader( "select 1;select 'Impossible' from sys.tables where 0=1;select 3",
              delegate( IDataReader r )
       {
             Assert.IsTrue( r.Read() );
             Assert.AreEqual( 1, r.GetInt32( 0 ) );
             Assert.IsTrue( r.NextResult(), "There is a 2nd result..." );
             Assert.IsFalse( r.Read(), "...but it is empty." );
             Assert.IsTrue( r.NextResult(), "There is a 3rd result..." );
             Assert.IsTrue( r.Read() );
             Assert.AreEqual( 3, r.GetInt32( 0 ) );
             Assert.IsFalse( r.Read() );
             Assert.IsFalse( r.NextResult() );
             Assert.IsFalse( r.IsClosed );
       } );
}

Cela vaut bien un long discours, non ?
En tous cas, cela vaut mieux que la documentation MSDN ci-dessous qui annonce faussement qu’après l’appel à NextResult, le lecteur est positionné sur le premier enregistrement. J’ai signalé l’erreur (après m’être créé un login .Net passport suite à des années de résistance).

Je vais pouvoir compter les jours avant la correction Smile.
 
Update jeudi 8 octobre 2009 : Il n’y a toujours pas eu de corrections mais une contribution supplémentaire de David Matson qui considère que l’interprétation de la remarque doit se comprendre au niveau de chaque result set et non au niveau des lignes.

Il a complètement raison ce monsieur !

Outlook et ses plugins…

Un ami a un problème de mail à supprimer automatiquement selon certains critères : les mails contiennent un identifiant et une révision, il ne faut garder pour un identifiant que la dernière révision. Le travail est à effectuer répertoire par répertoire au choix de l’utilisateur.  L’occasion pour moi de jeter un œil sur VSTO (Visual Studio Tool for Office) qui permet de développer (en .Net) des plugins pour Office.
Il y a quelques années, j’avais utilisé les Primary Interop Assemblies (PIAs). Cela fonctionnait bien que la programmation en C# soit assez désagréable (beaucoup de paramètres optionnels, d’où une utilisation intensive de System.Reflection.Missing.Value). VB.Net est certainement plus adapté.
 
En résumé, les modèles de projets de VS2008 (Add New Project > Visual C # > Office > 2003 ou 2007) utilisent les PIA et fournissent une couche supplémentaire autour des interfaces COM des applications Office. Apparemment, une attention toute particulière a été portée aux référencements circulaires des objets et autres joyeusetés qui ont toujours fait de ce type de développement un chemin de croix (les bons jours). Voir par exemple Outlook Shutdown and VSTO Add-ins.
En terme de développement, pas de souci majeur : sur ma machine, en ciblant Outlook 2007, le debugger lance Outlook, pas de processus oublié en mémoire. Rien à dire.
 
Mais, malheureusement, il faut cibler Outlook 2003. Alors qu’il semble trivial de déployer vers des Office 2007, faire un installeur pour la version précédente d’Office c’est tout un plaisir. D’abord, mauvaise surprise lorsque l’on rouvre le projet 2003 : il est automatiquement mis à jour en 2007 ! La solution est ici : Using VS 2008 to continue working on Office 2003 Add-ins. Ensuite, pour l’installation sur les postes clients, c’est beaucoup plus délicat. Voir Deploying Office 2003 Solutions with Windows Installer, et surtout, bonne chance.