Showing posts with label Opera. Show all posts
Showing posts with label Opera. Show all posts

2012/02/09

Vendor Css prefixes

This post tries to explain my points of view as a web developer about the Vendor prefixes issue:

Introduction

Css prefixes for experimental features are fine. They are a clear signal that something is being tested but it's not a standard, they allow browser vendors to create new features and test them without having to wait for the approval from everyone else, they allow to check how they can be used and whether they create unexpected problems or not.

So yes to keep vendor prefixes alive.

But...

The problem is when those prefixes spread all around the web and people start using them all over the place. Then the browser vendor will argue that they can't change them as people are already relying on them, other vendors have to provide their own implementation (with their prefixes) to avoid being left behind but even if they provide those same features they see that people are testing only on the native browsers of iPhones and Androids, both based on Webkit.

And this is the current situation: non-webkit browsers areclaiming that the use and abuse of -webkit is so strong that they are being forced to support in their own engines some -webkit features.

My opinion about how to deal with it

First rule should be that no browser manufacturer is allowed to ship experimental features in release versions of their browsers. Those experimental features should be restricted only to the alphas and betas, when a final version is released, it should allow only the unprefixed version if that feature has gained traction and it has been "approved" or don't ship it at all.

With "approved" I don't mean that it requires a final status, just a state where other vendors agree that it can be useful, it will be (or it's already) implemented that way by them and of course there's a spec stating how does it work.

Of course this is a big problem, you'll see them screaming and kicking around before accepting this. They prefer to keep on doing their little tricks and pushing whatever it's in their heads and releasing it to the wild to give them an advantage over the competition. But the fact is that even if this is the standarized way to create propietary features, we're back to the Netscape 3 era where each browser introduced new features without caring about the other one and then that other browser had to copy and replicate the successful features of the first one.

We know that it was bad for the developers, why is that good now?

Second step:

When a browser replicates the propietary feature of another one, it should support ASAP the feature both with their own prefix as well as the unprefixed one. If the feature is not good enough or not clearly defined to support the unprefixed version then it's obvious that it doesn't make sense to support the prefix from the other browser.

That would allow web developers to start testing the prefixed version from the first vendor and if it's clear that it's good enough they can put only the unprefixed version and all the browsers will be handled at once.

Third course of action:

Evangelism on sites that teach new CSS3 features, they shouldn't generate anything with a vendor prefix, just use the standard so it can work in all the browsers.

I don't think that most of the web developers are going to write huge amounts of css by themselves, instead they will look at some generator for backgrounds, buttons, patterns and copy whatever it's there. They might not know what's a vendor prefix, they just see that they can copy that code and it works. Given that some vendors (I'm looking at you Microsoft) make it really difficult to test the new versions of their browsers, you can't expect the developers to remember to add the -ms all around because they won't notice if it works or not; they just know that they have to keep on supporting IE8 for a number of years and that it doesn't support fancy things so they won't care too much about what's next for the few ones willing to buy a new computer.

I know that almost no one will care about my opinion, but at least I hope that someone else in the whole world agrees with me that these would be good steps.

 

 

2011/07/31

Third version for seamless replacement of textareas

Sometime ago I wrote a script that allows to use CKEditor, and at the same time keep using older scripts existing on the page that relied on reading the textarea.value (or even writing it), without having to modify them to use the CKEditor API.

The initial version worked with Firefox and Internet Explorer 8, and shortly after I added some adjustments so that Opera was also supported.

Recently a comment in that second post stated that the script gave errors in Chrome, although it tries to detect if the API that I was using is supported, but of course, webkit guys decided to implement the API but not make it available for native properties. Their statement is that treating native properties as overridable would have a negative effect on performance, and as we all know it's much more important to have a fast browser than a browser that allows the developers to do new things; unless you're in their team then you can write a new API for whatever you need and everyone else should use this API because it will change the world, you'll no longer have to use Flash because now you have this API to overcome other problems that we didn't want to fix. Besides the bug tickets commented previously, here's another one: bug 36423

Ok, enough ranting.

As I said, I've worked to find out the problems and the funny fact about this new version is that it works in Chrome but it still fails in Safari 5.1. So you can use it in your site if can restrict the browser used by your users to IE8+, Firefox 3.5+ , Opera 10+, and Chrome 12+ (I don't really know the oldest version of Firefox, Opera and Chrome where this will work, but I wouldn't expect anyone to use old versions of those browsers)


// Modify the default methods in CKEDITOR.dom.element to use .nativeValue if it's available
CKEDITOR.dom.element.prototype.getValue = function()
{
    if (typeof(this.$.nativeValue) == "undefined")
        return this.$.value;

    return this.$.nativeValue;
}

CKEDITOR.dom.element.prototype.setValue = function( value )
{
    if (typeof(this.$.nativeValue) == "undefined")
        this.$.value = value;
    else
        this.$.nativeValue = value;

    return this;
}

// Hook each textarea with its editor
CKEDITOR.on('instanceCreated', function(e) {
 if (e.editor.element.getName()=="textarea")
 {
  var node = e.editor.element.$;

  // If the .nativeValue hasn't been set for the textarea try to do it now
  if (typeof node.nativeValue == "undefined")
  {
   // for Opera & Firefox
   if (!DefineNativeValue(node))
   {
    // IE8 & Webkit
    if (!DefineValueProperty(node))
    {
     alert("Your browser is buggy. You should upgrade to something newer")
     return;
    }
   }
  }

  node.editor = e.editor;

  // House keeping.
  e.editor.on('destroy', function(e) {
   if (node.editor)
    delete node.editor;
  });
 }
});

// This function alters the behavior of the .value property to work with CKEditor
// It also provides a new property .nativeValue that reflects the original .value
// It can be used with HTMLTextAreaElement.prototype for Firefox, but Opera needs to call it on a textarea instance
function DefineNativeValue(node)
{
 if (!node.__lookupGetter__)
  return false;

    var originalGetter = node.__lookupGetter__("value");
    var originalSetter = node.__lookupSetter__("value");
    if (originalGetter && originalSetter)
    {
        node.__defineGetter__("value", function() {
                // if there's an editor, return its value
                if (this.editor)
                    return this.editor.getData();
                // else return the native value
                return originalGetter.call(this);
                }
            );
        node.__defineSetter__("value", function(data) {
                // If there's an editor, set its value
                if (this.editor) this.editor.setData(data);
                // always set the native value
                originalSetter.call(this, data)
                }
            );

        node.__defineGetter__("nativeValue", function() {
                return originalGetter.call(this);
                }
            );
        node.__defineSetter__("nativeValue", function(data) {
                originalSetter.call(this, data)
                }
            );
        return true
    }
    return false;
}

function DefineValueProperty(node)
{
    var originalValuepropDesc = Object.getOwnPropertyDescriptor(node, "value");

 if (!originalValuepropDesc)
  return false;

 // Safari doesn't allow to overwrite the property (but Chrome does)
 if (!originalValuepropDesc.configurable)
  return false;

    Object.defineProperty(node, "nativeValue",
            {
                get: function() {
                    return ( originalValuepropDesc.get ? originalValuepropDesc.get.call(this) : originalValuepropDesc.value );
                },
                set: function(data) {
                    originalValuepropDesc.set ? originalValuepropDesc.set.call(this, data) : originalValuepropDesc.value = data;
                }
   }
        );

    Object.defineProperty(node, "value",
            {
                get: function() {
                    // if there's an editor, return its value
                    if (this.editor)
                        return this.editor.getData();
                    // else return the native value
                    return this.nativeValue;
                },
                set: function(data) {
                    // If there's an editor, set its value
                    if (this.editor) this.editor.setData(data);
                    // always set the native value
                    this.nativeValue = data;
                }
            }
        );
 return true;
}

// Detection, not really needed, but it can help troubleshoting.
if (Object.defineProperty)
{
    // IE 8 and updated webkits
 // Detect Safari
 if (document.head)
 {
  var test = Object.getOwnPropertyDescriptor(document.head, "innerHTML");
  // IE9
  if (!test)
  {
   if (!DefineValueProperty(HTMLTextAreaElement.prototype))
    alert("Unable to define property override on the prototype");
  }
  else
   if (!test.configurable)
    alert("Safari doesn't allow to overwrite native properties");
 }
}
    else if (document.__defineGetter__)
{
    // FF 3.5 and Opera 10
 // We try to get the innerHTML getter for the body, if it works then getting the value for each textarea will work
 // Detect old webkits
 if (!document.body.__lookupGetter__("innerHTML"))
  alert("Old webkits don't allow to read the originalGetter and Setter for the textarea value");
}
    else
{
    // detect IE8 in compatibility mode...
    if (document.documentMode)
        alert("The page is running in Compatibility Mode (" + document.documentMode + "). Fix that")
    else
        alert("Your version of IE is too old");
}

 

2011/02/06

Creating extensions for each browser

These last days I've tried to create a similar extension for each browser, and this is a little recap of the first differences among them. A page with a full list of differences can be found in the State of the Add-on Developer Union

Firefox

In Firefox there are two options:

  1. Create the extension using the "classic" way that goes back to the very beginning and provides full access to anything in Firefox, you can customize it in any way that you like.
  2. Use the new Add-Ons SDK, only for Firefox 4. These ones doesn't require a restart and it's supposed to be future-proof.

In order to create a classic add-on you can get a skeleton using the Add-on Builder . There are lots of info about how to create these extensions and the API that they can use, and to start creating an Add-on the first step is this page to setup the environment. All you need is a text editor, some patience and be careful following the instructions. You can then configure Firefox to load the extension from the disk but when you make changes to the code you'll have to use an extension to force a reload of all the Firefox code or directly restart it; not nice.

To get started building add-ons with the new SDK, check the tutorial.
First problem: You are required to have Python installed.
What?
I'm so tired of projects that say: you have to install this or that or all of these projects just to get started.
What's so special about Python and the SDK environment that can't be done inside Firefox?
Why should I have to keep the list of instructions instead of being able to click on some buttons "Add extension", "Run tests", "Install", "Reload", etc...
Big boo here.
Maybe they want us to use wget instead of Firefox if they think that command line is so nice.

Anyway, I gave it a go in the Mac (I won't bother trying to install python in windows, it has already too many SDKs there for this and that), and so I followed the basic example, while trying at the same time to adjust it to my goal, I add the files, and when I do the "cfx run" then I get a cryptic error "raise ValueError('invalid resource hostname: %s' % name)". WTF? check again that I haven't done anything too weird. Nops. Then put back exactly the sample code. Nops. Search for the message. Bingo!, although there's no warning about this in the tutorial, you CAN'T use uppercase letters in the folder name. Hello Mozilla, it's 2011, I thought that the problems with uppercase and lowercase were gone long ago since MS-DOS. Why aren't your wonderful Python scripts able to deal with them?
Another big fail.

As a summary: I got a bad taste of the Add-ons SDK. When they put forward something that isn't oriented to Python lovers I might test it again (please note that I haven't even tested the API, just the environment makes me go back to keep using the classic method)

Chrome

It seems that there's a lot of work going on here in order to provide a very complete API for the extensions as well as good documentation and lots of examples. You can see a very basic one here and everything is quite easy to follow. Given the fast path that it's set on the release of new Chrome versions we can expect that this trend will continue and that they will add missing features to make some tasks even easier.

The only thing that you need is a text editor and Chrome, and by following the instructions you can get it working. It's not so powerful as the classic extensions in Firefox, but it's really easy to start creating an extension and there are lots of examples to check how they work.

Safari

It seems again that there's plenty of documentation at a central place, but just in the first page there's something strange:

Important: To develop extensions for Safari, you need to sign up for the Safari developer program online, at http://developer.apple.com. You need a signed certificate before your extension can be installed.

Uh?
So to create an sample extension in my own computer I need a certificate?

Ok, this shows again that Apple is a control freak. Why do I have to register to get an Apple ID and then get enrolled into a "Safari developer" program so that I can finally request a signing certificate, just to create a test extension? I can understand that in order to deploy it I might need to sign it, but I really should be able to create an extension for myself without the need to sign two looong agreements with Apple (I'm not sure if there was a paragraph about my first newborn there)

Anyway I go ahead and get a certificate in the Mac, and then it seems to work, but I would like to keep the development environment in Windows, so I try to install the downloaded *.cer there but anyway Safari keeps saying that there's no signing certificate installed. After a little while and some digging I realize that I must export the private signing key from the Mac KeyChain to Windows and once I installed it there Safari started working.

In some regards this might be the simpler interface of all the browsers in order to create an extension as you get a dialog to setup the basic configuration of the extension instead of editing them in a text file. The way that the code is setup is similar to Chrome (and Opera): background pages, injected scripts, so porting from Chrome wasn't a hard task.

As it happens with almost everything Apple does, if you are satisfied with what they offer, they try to make it quite easy, but if you want something extra then it's a no go.

Opera

I started checking the Opera API with the very first builds where they started adding support for extensions. In those days the API wasn't complete, there were a bunch of things that didn't work at all, so due to my hurry to test it I became a little frustrated because there were so many things missing, there was almost no documentation at the moment so it required a lot of effort to try to make something work because the examples were quite basic.

Now they have improved everything a lot, the behavior is similar to Chrome and Safari, but this is still a fresh SDK, so there are still missing features available in other browsers. They have written now lots of documentation and I guess that very shortly it should be quite easy to port an extension between Opera, Chrome and Safari due to the convergence of APIs that the developers find useful.

For the moment Opera it's lacking an API call similar to the ones that I've used for the other browsers so I will put the extension for browser at rest waiting for them to include them instead of struggle again and get tired of finding that it's not possible in any way.

2011/01/24

HTML5 Video plugin for CKEditor

You might have heard a lot of people talking about the new features of HTML5, and one that has brought a lot of interest is the <video> element that some people think that will avoid the use of Flash once and for all (at least Steve Jobs thinks so).

Personally I don't think that it will be so easy to dismiss a whole environment like Flash just because videos now can be played without external plugins, leaving aside the fight of h264 vs WebM (or as is now: Apple & MS vs the rest of the web), Flash has a lot of features that aren't easy to replace, the most basic one, the fact that you deploy a single swf file, vs having to provide several javascript and images just to perform anything basic with javascript. And as soon as you want to use some effects, you might end up with something huge and in fact as slow as the dreaded flash files.

But back to the topic, HTML5 video and CKEditor. What about that?

Ok, here we go, this is a plugin sponsored by DM Logic that makes using the <video> element as easy as an old <img>, you can insert, edit, use the context menu, etc... the dialog allows to specify the poster image, the dimensions (they are automatically adjusted according to the preview) and two source files, so that both the browsers that use the WebM format (Chrome, Firefox and Opera) and the ones that support only h264 (IE9 and Safari) can view your videos. Here's a short video showing how it works.

Download it here.

Updates:

v.1.3 26/08/12: Fixed problems with IE9, wrap the video in a <div> to avoid the problems with <p>. It's compatible with both CKEditor 3 and CKEditor 4

Demo

 

 

2009/12/12

Uploading .app files

Developing an extension for an application like CKEditor means that you have to be aware of the environment. CKEditor tries to cover the differences between the browsers and even adds workarounds for the bugs that are known in each one (as long as it's possible of course)

So when you are writting your code and something doesn't work as expected you have to start searching for bugs in your own code (the usual culprit), then you have to review how you are using the CKEditor API, verify that you have understood the explanation and that it matches the actual code that you have written. Then you escalate up the problem to the CKEditor source code, maybe there's some bug there that it's causing your problems. And sometimes you have to get even higher in the chain...

One of these situations is an issue that I've talked about in the post about uploads in CKEditor, I did try to apply an onChange listener for an uiElement with type="file", but it didn't work. After debugging the issue I found that despite the huge differences in DOM (this element was created as an iframe with a form and the <input type="file">), the event listeners aren't aware of this difference, so they apply the listeners to the container <div>.

This week I've been writing the code to fix this problem (that's the beauty of open source, when a problem is spotted it's possible to really check what's going on under the hood and create the patch without waiting for a company to include it in their plans), and after some tests it seemed to work: as soon as the user picks an image from their computer it is uploaded to the server and they just have to review the Alt attribute, the Class or the Title.

I had to review that the events are being reapplied correctly when the iframe recreates, so the tests (somewhat random) also included uploading non image files to verify that they are handled properly and everything still works.

Then I started to find that something was wrong, I selected an application, the file picker showed the name but there was no message. I debugged it further and the onChange event was being correctly fired (well, at first it was fired too many times due to a bug in the logic to append the listeners, but this is what these tests were about).

Maybe not CKEditor?

I had this form inside an iframe in a lot of nested code with lots of javascript, custom events and God knows what else. The next step was to create a simple testcase to focus on the problem:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
    <head>
    <title>Upload test</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <script type="text/javascript">
            function fileChanged(e)
            {
                var input = document.getElementById("upload");
                console.log("Selected file: " + input.value);
                //input.form.submit();
            }
        </script>
    </head>
<body>
    <form method="POST" enctype="multipart/form-data">
        <input type="file" name="upload" id="upload" onchange="fileChanged(event)">
        <input type="submit">
    </form>
</body>
</html>

This showed that I could select any file and it showed the name correctly and the form was posted by pressing the submit button or with the js call. But if I selected an application (I'm using currently a MacBook) then it showed the name and the form wasn't submitted. No error message, no warning, nothing.

The simple test removed CKEditor out of the equation and avoided long hours trying to find a non-existing problem in that code, and instead it pointed to some other basic issue with the browser and the upload of Applications (.app)

The current browser was Firefox 3.5, so I searched bugzilla but I wasn't able to find any report about this, the only bug that reported some strange problem was one about sending sometimes files with 0 size to the server.

So I added

                console.log("File size: " + input.files[0].fileSize);

and it showed the .app to be files of 0 size.

Something is quite wrong here!. I tested Safari and it seemed to allow the upload the .app but it showed the size as 102 bytes instead of several Mb.

Then I finally realized that an Application in Mac OS X is really a folder with subfolders and files, so this is the reason why it fails. Firefox accepts the .app in the filepicker but at the upload time it somehow sees that it is a folder (or that it isn't a real file) and rejects to send the form, but as I said, there's no warning about the problem. Testing with Opera did provide a message stating that the path pointed to some file that it couldn't find.

The most interesting situation was Safari, I changed the form to point to a real script that would handle the file and the result was that the application has been compressed on the fly and sent to the server as a .zip

How does each browser behave?

This is a live test with some code similar to the above:

So on one hand we have Firefox, it doesn't allow to upload the .app and doesn't provide any hint that there's a problem with the selected file. Also shows the size as 0.

Opera does its job much better and shows a tooltip on the <input type="file"> explaining that there's a problem there. Better but still not good enough as people might get confused about the reason of the problem. It hasn't implemented the API to get the size of a selected file.

Safari is quite different, if you select an .app then it will compress it and send it as a .zip so even if the user might find it a little strange that it has bothered to compress the data he finds that he was able to upload the file that he wanted. The only issue is that it shows the size as 102 instead of showing the same data that it's in the Finder.

The final test was the new beta of Google Chrome. Like Safari it showed the size of an .app as 102 bytes, but when I tried to upload it to the server (localhost) it just hanged. It showed a warning about a non responding page and no matter if I selected to close the page or ignore the warning. Chrome was dead and the only solution was to force quit it, even if the tab can be closed, something remains working there that didn't allow a clean exit.

So this can lead to one bug report for each browser (well Opera is almost OK, so it should be just a feature request), so much fun and job for a little test trying to find a bug in my code or CKEditor.