Gocept Weblog: Celebration: Zope 4 final release

TL;DR: Zope 4 beta phase ended, final version released!

After hard, long years of preparation Earl Zope now finally made it to get a permanent license for the Python 3 wonderland: In September 2016 almost 20 people started with the reanimation of Zope at the Zope Resurrection sprint. This marked the beginning of a wonderful journey for Earl Zope himself for the people who helped him. In August 2017 Earl Zope became aware that his Python 2 country will irreversibly be destroyed by 2020. Earl Zope was successfully applying for for a beta permission for the Python 3 wonderland in September 2017. This beta permission has been extended 9 times to give Earl Zope time to become a good citizen in his new home country.

Earl Zope says a big thank you to all who:

  • contributed to the Python 3 migration even before the resurrection sprint
  • wrote bug reports
  • fixed bugs
  • contributed time and/or money for the migration process
  • encouraged the developers
  • tested beta versions or even used them in production

To be welcome in the Python 3 wonderland many nuts had to be cracked:

  • porting of the code of Zope and its dependencies to Python 3
  • rewrite of RestrictedPython from scratch
  • develop a migration strategy for the ZODB contents aka Data.fs
  • polish the user interface of the Zope management interface (ZMI)
  • and many more…

Earl Zope is looking forward to a happy future in the Python 3 wonderland. Currently he did not yet give up his settling in the Python 2 land. This is planned to happen shortly before or after the Python 2 sunset in the beginning of 2020 when the son of Earl Zope IV becomes the new Earl Zope V. See the roadmap for details.

Planet Python

gamingdirectional: The final adjustment of the main menu page buttons

In this chapter, we will make a final adjustment of the main menu page buttons so they will be positioned nicely on the game scene. We need to edit three files in this chapter. We will adjust the button positions on the start scene class. from BgSprite import BgSprite from GameSprite import GameSprite from pygame.locals import * from pygame import math as mt import pygame import pygame.


Planet Python

gamingdirectional: Final touch up for the boy boundary detection mechanism

Sorry for not posting yesterday as I am terribly sick, although I am still sick today mine condition is a lot more better now. After the previous article we have basically developed a boundary detection mechanism for the player object and in this article, we will do the final touch up for that mechanism. Here are the final rules that we need to apply in order to complete the boundary detection…


Planet Python

codingdirectional: The final user interface of this video editing application

Sorry for not posting anything yesterday as I had suffered from the flu and today although I still feel a little bit tired and sick I need to at least post a post on this site. After a day of hard work I have finally finished to tidy up the user interface of this video editing application. I wish there is no bug in the logic part of this revised program. If you find any bug in the below program do let me know about it.

 from tkinter import * from tkinter import filedialog import os import subprocess import tkinter.ttk as tk  win = Tk() # Create tk instance win.title("NeW Vid") # Add a title win.resizable(0, 0) # Disable resizing the GUI win.configure(background='white') # change background color  mainframe = Frame(win) # create a frame mainframe.pack()  eqFrame = Frame(win) # create eq frame eqFrame.pack(side = TOP, fill=X)  animatedFrame = Frame(win) # create animated frame animatedFrame.pack(side = TOP, fill=X)  trimFrame = Frame(win) # create trim frame trimFrame.pack(side = TOP, fill=X)  buttonFrame = Frame(win) # create a button frame buttonFrame.pack(side = BOTTOM, fill=X, pady = 6)  # Create a label and scale box for eq contrast_variable = DoubleVar() contrast = Scale(eqFrame, from_=float(-2.00), to=float(2.00), orient=HORIZONTAL, label="CONTRAST", digits=3, resolution=0.01, variable=contrast_variable) contrast.set(1) contrast.pack(side = LEFT) brightness_variable = DoubleVar() brightness = Scale(eqFrame, from_=float(-1.00), to=float(1.00), orient=HORIZONTAL, label="BRIGHTNESS", digits=3, resolution=0.01, variable=brightness_variable) brightness.pack(side = LEFT) saturation_variable = DoubleVar() saturation = Scale(eqFrame, from_=float(0.00), to=float(3.00), orient=HORIZONTAL, label="SATURATION", digits=3, resolution=0.01, variable=saturation_variable) saturation.set(1) saturation.pack(side = LEFT) gamma_variable = DoubleVar() gamma = Scale(eqFrame, from_=float(0.10), to=float(10.00), orient=HORIZONTAL, label="GAMMA", digits=4, resolution=0.01, variable=gamma_variable) gamma.set(1) gamma.pack(side = LEFT) loop_variable = DoubleVar() loop = Scale(eqFrame, from_=float(0), to=float(10), orient=HORIZONTAL, label="REPEAT", digits=2, resolution=1, variable=loop_variable) loop.pack(side = LEFT) fr_variable = DoubleVar() fr = Scale(eqFrame, from_=float(9), to=float(60), orient=HORIZONTAL, label="FPS", digits=2, resolution=1, variable=fr_variable) fr.set(24) fr.pack(side = LEFT)  #create animated gif anime = Label(animatedFrame, text="Create Animated Image from Video   ") anime.pack(side = TOP) anime.pack(side = LEFT)  from_ = Label(animatedFrame, text="Start From (hour : minute : second)  ") from_.pack(side = BOTTOM) from_.pack(side = LEFT) from_t_h_varable = StringVar() from_t_h = Entry(animatedFrame, width=3, textvariable=from_t_h_varable) from_t_h.pack(side=BOTTOM) from_t_h.pack(side=LEFT) from_m = Label(animatedFrame, text=" : ") from_m.pack(side = BOTTOM) from_m.pack(side = LEFT) from_t_m_varable = StringVar() from_t_m = Entry(animatedFrame, width=3,textvariable=from_t_m_varable) from_t_m.pack(side=BOTTOM) from_t_m.pack(side=LEFT) from_s = Label(animatedFrame, text=" : ") from_s.pack(side = BOTTOM) from_s.pack(side = LEFT) from_t_s_varable = StringVar() from_t_s = Entry(animatedFrame, width=3,textvariable=from_t_s_varable) from_t_s.pack(side=BOTTOM) from_t_s.pack(side=LEFT)  to_ = Label(animatedFrame, text="  To (in second)  ") to_.pack(side = BOTTOM) to_.pack(side = LEFT) #to_t_h_varable = StringVar() #to_t_h = Entry(animatedFrame, width=3,textvariable=to_t_h_varable) #to_t_h.pack(side=BOTTOM) #to_t_h.pack(side=LEFT) #to_m = Label(animatedFrame, text=" : ") #to_m.pack(side = BOTTOM) #to_m.pack(side = LEFT) #to_t_m_varable = StringVar() #to_t_m = Entry(animatedFrame, width=3,textvariable=to_t_m_varable) #to_t_m.pack(side=BOTTOM) #to_t_m.pack(side=LEFT) #to_s = Label(animatedFrame, text=" : ") #to_s.pack(side = BOTTOM) #to_s.pack(side = LEFT) to_t_s_varable = StringVar() to_t_s = Entry(animatedFrame, width=3,textvariable=to_t_s_varable) to_t_s.pack(side=BOTTOM) to_t_s.pack(side=LEFT)   #trim video trim = Label(trimFrame, text="Trim Video   ") trim.pack(side = TOP) trim.pack(side = LEFT)  trim_from_ = Label(trimFrame, text="Start From (hour : minute : second)  ") trim_from_.pack(side = BOTTOM) trim_from_.pack(side = LEFT) trim_from_t_h_varable = StringVar() trim_from_t_h = Entry(trimFrame, width=3, textvariable=trim_from_t_h_varable) trim_from_t_h.pack(side=BOTTOM) trim_from_t_h.pack(side=LEFT) trim_from_m = Label(trimFrame, text=" : ") trim_from_m.pack(side = BOTTOM) trim_from_m.pack(side = LEFT) trim_from_t_m_varable = StringVar() trim_from_t_m = Entry(trimFrame, width=3,textvariable=trim_from_t_m_varable) trim_from_t_m.pack(side=BOTTOM) trim_from_t_m.pack(side=LEFT) trim_from_s = Label(trimFrame, text=" : ") trim_from_s.pack(side = BOTTOM) trim_from_s.pack(side = LEFT) trim_from_t_s_varable = StringVar() trim_from_t_s = Entry(trimFrame, width=3,textvariable=trim_from_t_s_varable) trim_from_t_s.pack(side=BOTTOM) trim_from_t_s.pack(side=LEFT)  trim_to_ = Label(trimFrame, text="  To (in second)  ") trim_to_.pack(side = BOTTOM) trim_to_.pack(side = LEFT) trim_to_t_h_varable = StringVar() trim_to_t_h = Entry(trimFrame, width=3,textvariable=trim_to_t_h_varable) trim_to_t_h.pack(side=BOTTOM) trim_to_t_h.pack(side=LEFT) trim_to_m = Label(trimFrame, text=" : ") trim_to_m.pack(side = BOTTOM) trim_to_m.pack(side = LEFT) trim_to_t_m_varable = StringVar() trim_to_t_m = Entry(trimFrame, width=3,textvariable=trim_to_t_m_varable) trim_to_t_m.pack(side=BOTTOM) trim_to_t_m.pack(side=LEFT) trim_to_s = Label(trimFrame, text=" : ") trim_to_s.pack(side = BOTTOM) trim_to_s.pack(side = LEFT) trim_to_t_s_varable = StringVar() trim_to_t_s = Entry(trimFrame, width=3,textvariable=trim_to_t_s_varable) trim_to_t_s.pack(side=BOTTOM) trim_to_t_s.pack(side=LEFT)  # Create a combo box vid_size = StringVar() # create a string variable preferSize = tk.Combobox(mainframe, textvariable=vid_size)  preferSize['values'] = (1920, 1280, 854, 640) # video width in pixels preferSize.current(0) # select item one  preferSize.pack(side = LEFT)  # Create a combo box vid_format = StringVar() # create a string variable preferFormat = tk.Combobox(mainframe, textvariable=vid_format)  preferFormat['values'] = ('.mp4', '.webm', '.avi', '.wmv', '.mpg', '.ogv') # video format preferFormat.current(0) # select item one  preferFormat.pack(side = LEFT)  removeAudioVal = IntVar() removeAudio = tk.Checkbutton(mainframe, text="Remove Audio", variable=removeAudioVal) removeAudio.pack(side = LEFT, padx=3)  newAudio = IntVar() aNewAudio = tk.Checkbutton(mainframe, text="New Audio", variable=newAudio) aNewAudio.pack(side = LEFT, padx=2)  count = 0 # counter uses to create multiple videos  # Open a video file def openVideo():                  fullfilename = filedialog.askopenfilename(initialdir="/", title="Select a file", filetypes=[("Video file", "*.mp4; *.avi ")]) # select a video file from the hard drive         audiofilename = ''                   if(fullfilename != ''):                   global count # access the global count variable                 scale_vid = preferSize.get() # retrieve value from the comno box                 new_size = str(scale_vid)                 dir_path = os.path.dirname(os.path.realpath(fullfilename))                  trim_video = False # set the trim video flag to false                  file_extension = fullfilename.split('.')[-1] # extract the video format from the original video                  os.chdir(dir_path) # change the directory to the original file's directory                  f = '_new_vid_' + new_size  + '.' + file_extension # the new output file name                 f2 = str(count)+f # second video                 f_gif = str(count) + f + '.gif' # create animated gif                  count += 1 # increase video counter for new video                  # create animated image from video                 animi_from_hour = from_t_h_varable.get()                 animi_from_minute = from_t_m_varable.get()                 animi_from_second = from_t_s_varable.get()                  #animi_to_hour = to_t_h_varable.get()                 #animi_to_minute = to_t_m_varable.get()                 animi_to_second = to_t_s_varable.get()                  # if the time areas are not empty and they have a digit then only the animated gif will be created                  if((animi_from_hour != '' and animi_from_hour.isdigit()) and (animi_from_minute != '' and animi_from_minute.isdigit()) and (animi_from_second != '' and animi_from_second.isdigit()) and (animi_to_second != '' and animi_to_second.isdigit())):                         subprocess.call(['ffmpeg', '-i', fullfilename, '-vf', 'scale=' + new_size + ':-1', '-y', f]) # resize video                         subprocess.call(['ffmpeg', '-i', f, '-vf', 'eq=contrast=' + str(contrast_variable.get()) +':brightness='+ str(brightness_variable.get()) +':saturation=' + str(saturation_variable.get()) +':gamma='+ str(gamma_variable.get()), '-y', f2]) # adjust the saturation, gamma, contrast and brightness of video                         subprocess.call(['ffmpeg', '-i', f2, '-ss', animi_from_hour + ':' + animi_from_minute + ':' + animi_from_second, '-t',  animi_to_second, '-y', f_gif]) # creating animated gif from starting point to end point                         os.remove(f)                         os.remove(f2)                         return 0                  if(newAudio.get() == 1):                         audiofilename = filedialog.askopenfilename(initialdir="/", title="Select a file", filetypes=[("Audio file", "*.wav; *.ogg ")]) # select a new audio file from the hard drive                             # video editing part start here                 noAudio = removeAudioVal.get() # get the checkbox state for audio                   subprocess.call(['ffmpeg', '-stream_loop', str(loop_variable.get()), '-i', fullfilename, '-vf', 'scale=' + new_size + ':-1', '-y', '-r', str(fr_variable.get()), f]) # resize, speedup and loop the video with ffmpeg                 subprocess.call(['ffmpeg', '-i', f, '-vf', 'eq=contrast=' + str(contrast_variable.get()) +':brightness='+ str(brightness_variable.get()) +':saturation=' + str(saturation_variable.get()) +':gamma='+ str(gamma_variable.get()), '-y', f2]) # adjust the saturation, gamma, contrast and brightness of video                  # trim video starting point and end point                 trim_from_hour = trim_from_t_h_varable.get()                 trim_from_minute = trim_from_t_m_varable.get()                 trim_from_second = trim_from_t_s_varable.get()                  trim_to_hour = trim_to_t_h_varable.get()                 trim_to_minute = trim_to_t_m_varable.get()                 trim_to_second = trim_to_t_s_varable.get()                  # if the time areas are not empty and they have a digit then trim the video                  if((trim_from_hour != '' and trim_from_hour.isdigit()) and (trim_from_minute != '' and trim_from_minute.isdigit()) and (trim_from_second != '' and trim_from_second.isdigit()) and (trim_to_second != '' and trim_to_second.isdigit()) and (trim_to_minute != '' and trim_to_minute.isdigit()) and (trim_to_hour != '' and trim_to_hour.isdigit())):                         subprocess.call(['ffmpeg', '-i', f2, '-ss', trim_from_hour + ':' + trim_from_minute + ':' + trim_from_second, '-t',  trim_to_hour + ':' + trim_to_minute + ':' + trim_to_second, '-y', '-c:v', 'copy', '-c:a', 'copy', f]) # trim the video from start to end point                         trim_video = True                  if(noAudio == 1 and trim_video == True):                         subprocess.call(['ffmpeg', '-i', f, '-c', 'copy', '-y', '-an', f2]) # remove audio from the original video                                          elif(noAudio == 1 and trim_video == False):                         subprocess.call(['ffmpeg', '-i', f2, '-c', 'copy', '-y', '-an', f]) # remove audio from the original video                                 if(audiofilename != '' and noAudio == 1 and newAudio.get() == 1 and trim_video == False):                         subprocess.call(['ffmpeg', '-i', f, '-i', audiofilename, '-shortest', '-c:v', 'copy', '-b:a', '256k', '-y', f2]) # add audio to the original video, trim either the audio or video depends on which one is longer                 elif(audiofilename != '' and noAudio == 1 and newAudio.get() == 1 and trim_video == True):                         subprocess.call(['ffmpeg', '-i', f2, '-i', audiofilename, '-shortest', '-c:v', 'copy',  '-b:a', '256k', '-y', f]) # add audio to the original video, trim either the audio or video depends on which one is longer                  f3 = f + vid_format.get() # The final video format                  if(f3.split('.')[-1] != f2.split('.')[-1] and trim_video == True and noAudio == 1 and newAudio.get() == 1 and audiofilename != ''):                         subprocess.call(['ffmpeg', '-i', f, '-y', f3]) # converting the video with ffmpeg                         os.remove(f2) # remove two videos                         os.remove(f)                 elif(f3.split('.')[-1] != f2.split('.')[-1] and trim_video == False and noAudio == 1 and newAudio.get() == 1 and audiofilename != ''):                         subprocess.call(['ffmpeg', '-i', f2, '-y', f3]) # converting the video with ffmpeg                         os.remove(f2) # remove two videos                         os.remove(f)                 elif(f3.split('.')[-1] != f2.split('.')[-1] and trim_video == False and noAudio != 1 and newAudio.get() != 1 and audiofilename == ''):                         subprocess.call(['ffmpeg', '-i', f2, '-y', f3]) # converting the video with ffmpeg                         os.remove(f2) # remove two videos                         os.remove(f)                 elif(f3.split('.')[-1] == f2.split('.')[-1] and trim_video == True and noAudio == 1 and audiofilename != ''):                         os.remove(f2) # remove one video                 elif(f3.split('.')[-1] == f2.split('.')[-1] and trim_video == True and noAudio != 1):                         os.remove(f2) # remove one video                 elif(f3.split('.')[-1] == f2.split('.')[-1] and trim_video == False and noAudio != 1):                         os.remove(f) # remove one video                 elif(f3.split('.')[-1] == f2.split('.')[-1] and trim_video == False and noAudio == 1):                         os.remove(f2) # remove one video                 else:                         os.remove(f) # remove one video                  trim_video = False # reset the trim video flag to false                  action_vid = tk.Button(buttonFrame, text="Open Video", command=openVideo) action_vid.pack(fill=X)  win.mainloop() 

The above program has included these two features, 1) Allows the user to trim the video from one point to another. 2) Includes the equalizer feature for the image as well as video. Below is one of the animated image which has been created with the above program.

We have one more thing needs to deal with which is the thread issue then we will be able to enjoy this application online together!

Planet Python

jQuery 3.0 Final Released!

jQuery 3.0 is now released! This version has been in the works since October 2014. We set out to create a slimmer, faster version of jQuery (with backwards compatibility in mind). We’ve removed all of the old IE workarounds and taken advantage of some of the more modern web APIs where it made sense. It is a continuation of the 2.x branch, but with a few breaking changes that we felt were long overdue. While the 1.12 and 2.2 branches will continue to receive critical support patches for a time, they will not get any new features or major revisions. jQuery 3.0 is the future of jQuery. If you need IE6-8 support, you can continue to use the latest 1.12 release.

Despite the 3.0 version number, we anticipate that these releases shouldn’t be too much trouble when it comes to upgrading existing code. Yes, there are a few “breaking changes” that justified the major version bump, but we’re hopeful the breakage doesn’t actually affect that many people.

To assist with upgrading, we have a brand new 3.0 Upgrade Guide. And the jQuery Migrate 3.0 plugin will help you to identify compatibility issues in your code. Your feedback on the changes will help us greatly, so please try it out on your existing code and plugins!

You can get the files from the jQuery CDN, or link to them directly:



You can also get the release from npm:

npm install jquery@3.0.0

In addition, we’ve got the release for jQuery Migrate 3.0. We highly recommend using this to address any issues with breaking changes in jQuery 3.0. You can get those files here:



npm install jquery-migrate@3.0.0

For more information about upgrading your jQuery 1.x and 2.x pages to jQuery 3.0 with the help of jQuery Migrate, see the jQuery Migrate 1.4.1 blog post.

Slim build

Finally, we’ve added something new to this release. Sometimes you don’t need ajax, or you prefer to use one of the many standalone libraries that focus on ajax requests. And often it is simpler to use a combination of CSS and class manipulation for all your web animations. Along with the regular version of jQuery that includes the ajax and effects modules, we’re releasing a “slim” version that excludes these modules. All in all, it excludes ajax, effects, and currently deprecated code. The size of jQuery is very rarely a load performance concern these days, but the slim build is about 6k gzipped bytes smaller than the regular version – 23.6k vs 30k. These files are also available in the npm package and on the CDN:



This build was created with our custom build API, which allows you to exclude or include any modules you like. For more information, have a look at the jQuery README.

Compatibility with jQuery UI and jQuery Mobile

While most things will work, there are a few issues that jQuery UI and jQuery Mobile will be addressing in upcoming releases. If you find an issue, keep in mind that it may already be addressed upstream and using the jQuery Migrate 3.0 plugin should fix it. Expect releases soon.


Major changes

Below are just the highlights of the major new features, improvements, and bug fixes in these releases, you can dig into more detail on the 3.0 Upgrade Guide. A complete list of issues fixed is available on our GitHub bug tracker. If you read the blog post for 3.0.0-rc1, the below features are the same.

jQuery.Deferred is now Promises/A+ compatible

jQuery.Deferred objects have been updated for compatibility with Promises/A+ and ES2015 Promises, verified with the Promises/A+ Compliance Test Suite. This meant we needed some major changes to the .then() method. Legacy behavior can be restored by replacing any use of .then() with the now-deprecated .pipe() method (which has an identical signature).

  1. An exception thrown in a .then() callback now becomes a rejection value. Previously, exceptions bubbled all the way up, aborting callback execution. Any deferreds relying on the resolution of the deferred that threw an exception would never have resolved.

  2. Example: uncaught exceptions vs. rejection values

     var deferred = jQuery.Deferred(); deferred.then(function() {   console.log("first callback");   throw new Error("error in callback"); }) .then(function() {   console.log("second callback"); }, function(err) {   console.log("rejection callback", err instanceof Error); }); deferred.resolve(); 

    Previously, “first callback” was logged and the error was thrown. All execution was stopped. Neither “second callback” nor “rejection callback” would have been logged. The new, standards-compliant behavior is that you’ll now see “rejection callback” and true logged. err is the rejection value from the first callback.

  3. The resolution state of a Deferred created by .then() is now controlled by its callbacks—exceptions become rejection values and non-thenable returns become fulfillment values. Previously, returns from rejection handlers became rejection values.

  4. Example: returns from rejection callbacks

     var deferred = jQuery.Deferred(); deferred.then(null, function(value) {   console.log("rejection callback 1", value);   return "value2"; }) .then(function(value) {   console.log("success callback 2", value);   throw new Error("exception value"); }, function(value) {   console.log("rejection callback 2", value); }) .then(null, function(value) {   console.log("rejection callback 3", value); }); deferred.reject("value1"); 

    Previously, this would log “rejection callback 1 value1”, “rejection callback 2 value2”, and “rejection callback 3 undefined”.

    The new, standards-compliant behavior is that this will log “rejection callback 1 value1”, “success callback 2 value2″, and “rejection callback 3 [object Error]”.

  5. Callbacks are always invoked asynchronously, even if a Deferred has already been resolved. Previously, these callbacks were executed synchronously upon binding.

  6. Example: async vs sync

     var deferred = jQuery.Deferred(); deferred.resolve(); deferred.then(function() {   console.log("success callback"); }); console.log("after binding"); 

    Previously, this would log “success callback” then “after binding”. Now, it will log “after binding” and then “success callback”.

Important: while caught exceptions had advantages for in-browser debugging, it is far more declarative (i.e. explicit) to handle them with rejection callbacks. Keep in mind that this places the responsibility on you to always add at least one rejection callback when working with promises. Otherwise, some errors might go unnoticed.

We’ve built a plugin to help in debugging Promises/A+ compatible Deferreds. If you are not seeing enough information about an error on the console to determine its source, check out the jQuery Deferred Reporter Plugin.

jQuery.when has also been updated to accept any thenable object, which includes native Promise objects.


Added .catch() to Deferreds

The catch() method was added to promise objects as an alias for .then(null, fn).


Error cases don’t silently fail

Perhaps in a profound moment you’ve wondered, “What is the offset of a window?” Then you probably realized that is a crazy question – how can a window even have an offset?

In the past, jQuery has sometimes tried to make cases like this return something rather than having them throw errors. In this particular case of asking for the offset of a window, the answer up to now has been { top: 0, left: 0 } With jQuery 3.0, such cases will throw errors so that crazy requests aren’t silently ignored. Please try out this release and see if there is any code out there depending on jQuery to mask problems with invalid inputs.


Removed deprecated event aliases

.load, .unload, and .error, deprecated since jQuery 1.8, are no more. Use .on() to register listeners.


Animations now use requestAnimationFrame

On platforms that support the requestAnimationFrame API, which is pretty much everywhere but IE9 and Android<4.4, jQuery will now use that API when performing animations. This should result in animations that are smoother and use less CPU time – and save battery as well on mobile devices.

jQuery tried using requestAnimationFrame a few years back but there were serious compatibility issues with existing code so we had to back it out. We think we’ve beaten most of those issues by suspending animations while a browser tab is out of view. Still, any code that depends on animations to always run in nearly real-time is making an unrealistic assumption.

Massive speedups for some jQuery custom selectors

Thanks to some detective work by Paul Irish at Google, we identified some cases where we could skip a bunch of extra work when custom selectors like :visible are used many times in the same document. That particular case is up to 17 times faster now!

Keep in mind that even with this improvement, selectors like :visible and :hidden can be expensive because they depend on the browser to determine whether elements are actually displaying on the page. That may require, in the worst case, a complete recalculation of CSS styles and page layout! While we don’t discourage their use in most cases, we recommend testing your pages to determine if these selectors are causing performance issues.

This change actually made it into 1.12/2.2, but we wanted to reiterate it for jQuery 3.0.


As mentioned above, the Upgrade Guide is now available for anyone ready to try out this release. Aside from being helpful in upgrading, it also lists more of the notable changes.



Thank you to everyone who helped with this release through code contributions, issue reports, and more, including but not limited to Jason Bedard, Fredrik Blomqvist, Leonardo Braga, Ralin Chimev, Jon Dufresne, Oleg Gaidarenko, Richard Gibson, Michał Gołębiowski, Scott González, Zack Hall, Alexander K, Martijn W. van der Lee, Alexander Lisianoi, Steve Mao, Dave Methvin, Jha Naman, Jae Sung Park, Todor Prikumov, William Robinet, Felipe Sateler, Damian Senn, Josh Soref, Jun Sun, Christophe Tafani-Dereeper, Vitaliy Terziev, Joe Trumbull, Bernhard M. Wiedemann, Devin Wilson, and Henry Wong.




  • Golf away 21 bytes (eaa3e9f)
  • Preserve URL hash on requests (#1732, e077ffb)
  • Execute jQuery#load callback with correct context (#3035, 5d20a3c)
  • Ensure ajaxSettings.traditional is still honored (#3023, df2051c)
  • Remove unnecessary use of jQuery.trim (0bd98b1)


  • Avoid infinite recursion on non-lowercase attribute getters (#3133, e06fda6)
  • Add a support comment & fix a link @ tabIndex hook (9cb89bf)
  • Strip/collapse whitespace for set values on selects (#2978, 7052698)
  • Remove redundant parent check (b43a368)
  • Fix setting selected on an option in IE<=11 (#2732, 780cac8)


  • Don’t workaround the IE 11 iframe-in-fullscreen sizing issues (#3041, ff1a082)
  • Toggle detached elements as visible unless they have display: none (#2863, 755e7cc)
  • Make sure elem.ownerDocument.defaultView is not null (#2866, 35c3148)
  • Add animation-iteration-count to cssNumber (#2792, df822ca)
  • Restore cascade-override behavior in .show (#2654, #2308, dba93f7)
  • Stop Firefox from treating disconnected elements as cascade-hidden (#2833, fe05cf3)



  • Separate the two paths in jQuery.when (#3029, 356a3bc)
  • Provide explicit undefined context for jQuery.when raw casts (#3082, 7f1e593)
  • Remove default callback context (#3060, 7608437)
  • Warn on exceptions that are likely programming errors (#2736, 36a7cf9)
  • Propagate progress correctly from unwrapped promises (#3062, d5dae25)
  • Make jQuery.when synchronous when possible (#3100, de71e97)
  • Remove undocumented progress notifications in $ .when (#2710, bdf1b8f)
  • Give better stack diagnostics on exceptions (07c11c0)


  • Add tests for negative borders & paddings (f00dd0f)


  • Fix various spelling errors (aae4411)
  • Update support comments related to IE (693f1b5)
  • Fix an incorrect comment in the attributes module (5430c54)
  • Updated links to https where they are supported. (b0b280c)
  • Update support comments to follow the new syntax (6072d15)
  • Use https where possible (1de8346)
  • Use HTTPS URLs for jsfiddle & jsbin (63a303f)
  • Add FAQ to reduce noise in issues (dbdc4b7)
  • Add a note about loading source with AMD (#2714, e0c25ab)
  • Add note about code organization with AMD (#2750, dbc4608)
  • Reference new feature guidelines and API tenets (#2320, 6054139)



  • Allow constructing a jQuery.Event without a target (#3139, 2df590e)
  • Add touch event properties, eliminates need for a plugin (#3104, f595808)
  • Add the most commonly used pointer event properties (7d21f02)
  • Remove fixHooks, propHooks; switch to ES5 getter with addProp (#3103, #1746, e61fccb)
  • Make event dispatch optimizable by JavaScript engines (9f268ca)
  • Evaluate delegate selectors at add time (#3071, 7fd36ea)
  • Cover invalid delegation selector edge cases (e8825a5)
  • Fix chaining .on() with null handlers (#2846, 17f0e26)
  • Remove pageX/pageY fill for event object (#3092, 931f45f)


  • Don’t execute native stop(Immediate)Propagation from simulation (#3111, 94efb79)



  • Resolve strict mode ClientRect “no setter” exception (3befe59)



  • Treat literal and function-returned null/undefined the same (#3005, 9fdbdd3)
  • Reduce size (91850ec)



  • Take Safari 9.1 into account (234a2d8)
  • Limit selection to #qunit-fixture in attributes.js (ddb2c06)
  • Set Edge’s expected support for clearCloneStyle to true (28f0329)
  • Fix Deferred tests in Android 5.0’s stock Chrome browser & Yandex.Browser (5c01cb1)
  • Add additional test for jQuery.isPlainObject (728ea2f)
  • Build: update QUnit and fix incorrect test (b97c8d3)
  • Fix manipulation tests in Android 4.4 (0b0d4c6)
  • Remove side-effects of one attributes test (f9ea869)
  • Account for new offset tests (f52fa81)
  • Make iframe tests wait after checking isReady (08d73d7)
  • Refactor testIframe() to make it DRYer and more consistent (e5ffcb0)
  • Weaken sync-assumption from jQuery.when to jQuery.ready.then (f496182)
  • Test element position outside view (#2909, a2f63ff)
  • Make the regex catching Safari 9.0/9.1 more resilient (7f2ebd2)


  • .not/.filter consistency with non-elements (#2808, 0e2f8f9)
  • Never let .closest() match positional selectors (#2796, a268f52)
  • Restore jQuery push behavior in .find (#2370, 4d3050b)

Official jQuery Blog