From d781eee575ffd5e05500e9e2e18c1a2a245a2339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis=20Roch=C3=A9?= Date: Fri, 22 Jan 2016 14:33:25 +0000 Subject: Fix typo indentation in CI projects' API --- doc/ci/api/projects.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/api/projects.md b/doc/ci/api/projects.md index 74a4c64d000..fe6b1c01352 100644 --- a/doc/ci/api/projects.md +++ b/doc/ci/api/projects.md @@ -18,7 +18,7 @@ GET /ci/projects Returns: ```json - [ +[ { "id" : 271, "name" : "gitlabhq", -- cgit v1.2.1 From a1e1e72375c6b8f511c779c74b3dd1073afff6c1 Mon Sep 17 00:00:00 2001 From: Warren Guy Date: Sat, 30 Jan 2016 18:30:51 +0800 Subject: Generate valid Message-ID in email rejection mailer Use a Message-ID that is RFC 2111 compliant. This fix is consistent with how the Message-ID is generated in the 'notify' mailer. --- app/mailers/email_rejection_mailer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/mailers/email_rejection_mailer.rb b/app/mailers/email_rejection_mailer.rb index 883f1c73ad4..76db31a4c45 100644 --- a/app/mailers/email_rejection_mailer.rb +++ b/app/mailers/email_rejection_mailer.rb @@ -10,7 +10,7 @@ class EmailRejectionMailer < BaseMailer subject: "[Rejected] #{@original_message.subject}" } - headers['Message-ID'] = SecureRandom.hex + headers['Message-ID'] = "<#{SecureRandom.hex}@#{Gitlab.config.gitlab.host}>" headers['In-Reply-To'] = @original_message.message_id headers['References'] = @original_message.message_id -- cgit v1.2.1 From 797bcdf43e19f159492b80ddc635f4bbcf6b41a1 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 31 Jan 2016 15:56:27 -0500 Subject: Add notes about the regression issues to CONTRIBUTING.md [ci skip] --- CONTRIBUTING.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1bd91f0bdce..a6c85e2a6bb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -177,6 +177,26 @@ is probably 1, adding a new Git Hook maybe 4 or 5, big features 7-9. issues or chunks. You can simply not set the weight of a parent issue and set weights to children issues. +### Regression issues + +Every monthly release has a corresponding issue on the CE issue tracker to keep +track of functionality broken by that release and any fixes that need to be +included in a patch release (see [8.3 Regressions] as an example). + +As outlined in the issue description, the intended workflow is to post one note +with a reference to an issue describing the regression, and then to update that +note with a reference to the merge request that fixes it as it becomes available. + +If you're a contributor who doesn't have the required permissions to update +other users' notes, please post a new note with a reference to both the issue +and the merge request. + +The release manager will [update the notes] in the regression issue as fixes are +addressed. + +[8.3 Regressions]: https://gitlab.com/gitlab-org/gitlab-ce/issues/4127 +[update the notes]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/pro-tips.md#update-the-regression-issue + ## Merge requests We welcome merge requests with fixes and improvements to GitLab code, tests, -- cgit v1.2.1 From 99492d6b8d01f8ec0e5c391532e364d06dbd41b4 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 27 Jan 2016 14:09:58 +0100 Subject: Optimize fetching issues closed by a merge request Instead of running ClosingIssueExtractor for every commit in a merge request we can gather all the commit messages (and the merge request description), concatenate all this together and then run ClosingIssueExtractor only once. The result of this is that MergeRequest#closes_issues is now between 3.5x and 4x faster than the old setup. Using a merge request with 10 commits (each referencing a number of issues to close) this reduced the call duration from around 200 milliseconds to around 50 milliseconds. As a result of these changes the Jira related tests for MergeRequest#closes_issues have been removed. These tests stubbed Commit#closes_issues meaning that the only code that was really tested was the call to Array#uniq to filter out duplicate issues. As this code is no longer used (nor present) the corresponding tests were removed. Related: gitlab-org/gitlab-ce#12419 --- CHANGELOG | 1 + app/models/merge_request.rb | 8 ++++---- spec/models/merge_request_spec.rb | 22 +++++++--------------- 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 14f2f14becd..051bc033117 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -26,6 +26,7 @@ v 8.4.2 track them in Performance Monitoring. - Increase contrast between highlighted code comments and inline diff marker - Fix method undefined when using external commit status in builds + - Optimized performance of finding issues to be closed by a merge request (Yorick Peterse) v 8.4.1 - Apply security updates for Rails (4.2.5.1), rails-html-sanitizer (1.0.3), diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 41dd248d80a..09af60a2016 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -346,10 +346,10 @@ class MergeRequest < ActiveRecord::Base # Return the set of issues that will be closed if this merge request is accepted. def closes_issues(current_user = self.author) if target_branch == project.default_branch - issues = commits.flat_map { |c| c.closes_issues(current_user) } - issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user). - closed_by_message(description)) - issues.uniq(&:id) + messages = commits.map(&:safe_message) << description + + Gitlab::ClosingIssueExtractor.new(project, current_user). + closed_by_message(messages.join("\n")) else [] end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 291e6200a5b..46f2f20b986 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -137,9 +137,10 @@ describe MergeRequest, models: true do describe 'detection of issues to be closed' do let(:issue0) { create :issue, project: subject.project } let(:issue1) { create :issue, project: subject.project } - let(:commit0) { double('commit0', closes_issues: [issue0]) } - let(:commit1) { double('commit1', closes_issues: [issue0]) } - let(:commit2) { double('commit2', closes_issues: [issue1]) } + + let(:commit0) { double('commit0', safe_message: "Fixes #{issue0.to_reference}") } + let(:commit1) { double('commit1', safe_message: "Fixes #{issue0.to_reference}") } + let(:commit2) { double('commit2', safe_message: "Fixes #{issue1.to_reference}") } before do allow(subject).to receive(:commits).and_return([commit0, commit1, commit2]) @@ -149,7 +150,9 @@ describe MergeRequest, models: true do allow(subject.project).to receive(:default_branch). and_return(subject.target_branch) - expect(subject.closes_issues).to eq([issue0, issue1].sort_by(&:id)) + closed = subject.closes_issues + + expect(closed).to include(issue0, issue1) end it 'only lists issues as to be closed if it targets the default branch' do @@ -167,17 +170,6 @@ describe MergeRequest, models: true do expect(subject.closes_issues).to include(issue2) end - - context 'for a project with JIRA integration' do - let(:issue0) { JiraIssue.new('JIRA-123', subject.project) } - let(:issue1) { JiraIssue.new('FOOBAR-4567', subject.project) } - - it 'returns sorted JiraIssues' do - allow(subject.project).to receive_messages(default_branch: subject.target_branch) - - expect(subject.closes_issues).to eq([issue0, issue1]) - end - end end describe "#work_in_progress?" do -- cgit v1.2.1 From 1256dabb445211fe48f1d53cc5be15ad99793178 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 1 Feb 2016 13:54:39 +0100 Subject: Instrument all Gitlab::Git instance methods --- config/initializers/metrics.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index 0945b93ed5d..e69552631ff 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -49,12 +49,14 @@ if Gitlab::Metrics.enabled? config.instrument_instance_methods(Gitlab::Shell) config.instrument_methods(Gitlab::Git) - config.instrument_instance_methods(Gitlab::Git::Repository) Gitlab::Git.constants.each do |name| const = Gitlab::Git.const_get(name) - config.instrument_methods(const) if const.is_a?(Module) + next unless const.is_a?(Module) + + config.instrument_methods(const) + config.instrument_instance_methods(const) end Dir[Rails.root.join('app', 'finders', '*.rb')].each do |path| -- cgit v1.2.1 From 33fb55a572364feca70fcc0f15b80da6176f4d71 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 1 Feb 2016 15:03:58 +0100 Subject: Instrument various Rugged constants --- config/initializers/metrics.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index e69552631ff..3e1deb8d306 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -64,6 +64,16 @@ if Gitlab::Metrics.enabled? config.instrument_instance_methods(const) end + + [ + :Blame, :Branch, :BranchCollection, :Blob, :Commit, :Diff, :Repository, + :Tag, :TagCollection, :Tree + ].each do |name| + const = Rugged.const_get(name) + + config.instrument_methods(const) + config.instrument_instance_methods(const) + end end GC::Profiler.enable -- cgit v1.2.1 From 72bd004b3114fad43feaa7d21e0c2cde4b5b6a0d Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 1 Feb 2016 16:20:49 +0100 Subject: Allow "@" in file names and path --- lib/gitlab/regex.rb | 8 ++++---- spec/lib/gitlab/regex_spec.rb | 8 ++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 53ab2686b43..5c35c5b1450 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -44,19 +44,19 @@ module Gitlab def file_name_regex - @file_name_regex ||= /\A[a-zA-Z0-9_\-\.]*\z/.freeze + @file_name_regex ||= /\A[a-zA-Z0-9_\-\.\@]*\z/.freeze end def file_name_regex_message - "can contain only letters, digits, '_', '-' and '.'. " + "can contain only letters, digits, '_', '-', '@' and '.'. " end def file_path_regex - @file_path_regex ||= /\A[a-zA-Z0-9_\-\.\/]*\z/.freeze + @file_path_regex ||= /\A[a-zA-Z0-9_\-\.\/\@]*\z/.freeze end def file_path_regex_message - "can contain only letters, digits, '_', '-' and '.'. Separate directories with a '/'. " + "can contain only letters, digits, '_', '-', '@' and '.'. Separate directories with a '/'. " end diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index d67ee423b9b..c51b10bdc69 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -21,4 +21,12 @@ describe Gitlab::Regex, lib: true do it { expect('Dash – is this').to match(Gitlab::Regex.project_name_regex) } it { expect('?gitlab').not_to match(Gitlab::Regex.project_name_regex) } end + + describe 'file name regex' do + it { expect('foo@bar').to match(Gitlab::Regex.file_name_regex) } + end + + describe 'file path regex' do + it { expect('foo@/bar').to match(Gitlab::Regex.file_path_regex) } + end end -- cgit v1.2.1 From f86ddfd36538667cd0c484a62825569a36ef2a2c Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 12 Sep 2015 20:54:06 -0700 Subject: Render sanitized SVG images Closes https://github.com/gitlabhq/gitlabhq/issues/9265 --- CHANGELOG | 1 + Gemfile | 3 +++ Gemfile.lock | 1 + app/helpers/blob_helper.rb | 12 ++++++++++++ app/views/projects/blob/_blob.html.haml | 5 ++++- features/project/source/browse_files.feature | 10 ++++++++++ features/steps/project/source/browse_files.rb | 17 +++++++++++++++++ spec/fixtures/logo_sample.svg | 27 +++++++++++++++++++++++++++ 8 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/logo_sample.svg diff --git a/CHANGELOG b/CHANGELOG index 58efbe2db7b..d7d07cd1c46 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.5.0 (unreleased) - Ensure rake tasks that don't need a DB connection can be run without one - Add "visibility" flag to GET /projects api endpoint - Ignore binary files in code search to prevent Error 500 (Stan Hu) + - Render sanitized SVG images (Stan Hu) - Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push - New UI for pagination - Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet diff --git a/Gemfile b/Gemfile index a09d44f8bfd..c9d428a1798 100644 --- a/Gemfile +++ b/Gemfile @@ -179,6 +179,9 @@ gem "underscore-rails", "~> 1.8.0" gem "sanitize", '~> 2.0' gem 'babosa', '~> 1.0.2' +# Sanitizes SVG input +gem "loofah", "~> 2.0.3" + # Protect against bruteforcing gem "rack-attack", '~> 4.3.1' diff --git a/Gemfile.lock b/Gemfile.lock index ec92964df25..b4f7587c419 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -953,6 +953,7 @@ DEPENDENCIES jquery-ui-rails (~> 5.0.0) kaminari (~> 0.16.3) letter_opener (~> 1.1.2) + loofah (~> 2.0.3) mail_room (~> 0.6.1) method_source (~> 0.8) minitest (~> 5.7.0) diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 694c03206bd..16967927922 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -126,4 +126,16 @@ module BlobHelper blob.size end end + + def blob_svg?(blob) + blob.language && blob.language.name == 'SVG' + end + + # SVGs can contain malicious JavaScript; only include whitelisted + # elements and attributes. Note that this whitelist is by no means complete + # and may omit some elements. + def sanitize_svg(blob) + blob.data = Loofah.scrub_fragment(blob.data, :strip).to_xml + blob + end end diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml index 3d8d88834e2..2c5b8dc4356 100644 --- a/app/views/projects/blob/_blob.html.haml +++ b/app/views/projects/blob/_blob.html.haml @@ -35,7 +35,10 @@ - if blob.lfs_pointer? = render "download", blob: blob - elsif blob.text? - = render "text", blob: blob + - if blob_svg?(blob) + = render "image", blob: sanitize_svg(blob) + - else + = render "text", blob: blob - elsif blob.image? = render "image", blob: blob - else diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index a8c276b949e..1e09dbc4c8f 100644 --- a/features/project/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -320,3 +320,13 @@ Feature: Project Source Browse Files Then I should see download link and object size And I should not see lfs pointer details And I should see buttons for allowed commands + + @javascript + Scenario: I preview an SVG file + Given I click on "Upload file" link in repo + And I upload a new SVG file + And I fill the upload file commit message + And I fill the new branch name + And I click on "Upload file" + Given I visit the SVG file + Then I can see the new rendered SVG image diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index d08935aa101..13caddc44a4 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -351,6 +351,19 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps expect(page).to have_content "You're not allowed to make changes to this project directly. A fork of this project has been created that you can make changes in, so you can submit a merge request." end + # SVG files + step 'I upload a new SVG file' do + drop_in_dropzone test_svg_file + end + + step 'I visit the SVG file' do + visit namespace_project_blob_path(@project.namespace, @project, 'new_branch_name/logo_sample.svg') + end + + step 'I can see the new rendered SVG image' do + expect(find('.file-content')).to have_css('img') + end + private def set_new_content @@ -410,4 +423,8 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps def test_image_file File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') end + + def test_svg_file + File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg') + end end diff --git a/spec/fixtures/logo_sample.svg b/spec/fixtures/logo_sample.svg new file mode 100644 index 00000000000..883e7e6cf92 --- /dev/null +++ b/spec/fixtures/logo_sample.svg @@ -0,0 +1,27 @@ + + + + Slice 1 + Created with Sketch. + + + + + + -- cgit v1.2.1 From ea11f0218a0cb8c23091965989a5b0875fd1daae Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 1 Feb 2016 15:48:59 -0500 Subject: Update CHANGELOG [ci skip] --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index c79c7d527c3..7a70516173c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ v 8.5.0 (unreleased) - Display 404 error on group not found - Track project import failure - Fix visibility level text in admin area (Zeger-Jan van de Weg) + - Warn admin during OAuth of granting admin rights (Zeger-Jan van de Weg) - Update the ExternalIssue regex pattern (Blake Hitchcock) - Optimized performance of finding issues to be closed by a merge request - Revert "Add IP check against DNSBLs at account sign-up" @@ -48,7 +49,6 @@ v 8.4.1 and Nokogiri (1.6.7.2) - Fix redirect loop during import - Fix diff highlighting for all syntax themes - - Warn admin during OAuth of granting admin rights (Zeger-Jan van de Weg) - Delete project and associations in a background worker v 8.4.0 -- cgit v1.2.1 From 933834c4a60610710fc949daa2e15531c2c45780 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 1 Feb 2016 16:55:00 -0500 Subject: Allow manual resize of js-autosize textareas First, the autosize library was being too controlling and removed the `resize` property from any elements to which it was attached, removing the drag handle. Second, we detect when the user manually resizes an autosize textarea, and then remove the autosize behavior from it and increase its max-height. This should allow for the best of both worlds. Closes #12832 --- .../javascripts/behaviors/autosize.js.coffee | 20 +- .../stylesheets/framework/markdown_area.scss | 2 +- app/assets/stylesheets/pages/note_form.scss | 2 +- spec/javascripts/behaviors/autosize_spec.js.coffee | 11 + vendor/assets/javascripts/jquery.ba-resize.js | 246 +++++++++++++++++++++ 5 files changed, 278 insertions(+), 3 deletions(-) create mode 100644 spec/javascripts/behaviors/autosize_spec.js.coffee create mode 100644 vendor/assets/javascripts/jquery.ba-resize.js diff --git a/app/assets/javascripts/behaviors/autosize.js.coffee b/app/assets/javascripts/behaviors/autosize.js.coffee index b32072e61ee..a072fe48a98 100644 --- a/app/assets/javascripts/behaviors/autosize.js.coffee +++ b/app/assets/javascripts/behaviors/autosize.js.coffee @@ -1,4 +1,22 @@ +#= require jquery.ba-resize #= require autosize $ -> - autosize($('.js-autosize')) + $fields = $('.js-autosize') + + $fields.on 'autosize:resized', -> + $field = $(@) + $field.data('height', $field.outerHeight()) + + $fields.on 'resize.autosize', -> + $field = $(@) + + if $field.data('height') != $field.outerHeight() + $field.data('height', $field.outerHeight()) + autosize.destroy($field) + $field.css('max-height', window.outerHeight) + + autosize($fields) + autosize.update($fields) + + $fields.css('resize', 'vertical') diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 6732343802a..1d8611b04dc 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -83,7 +83,7 @@ background: #FFF; border: 1px solid #ddd; min-height: 140px; - max-height: 430px; + max-height: 500px; padding: 5px; box-shadow: none; width: 100%; diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 32ba1676333..158c2a47862 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -147,7 +147,7 @@ .edit_note { .markdown-area { min-height: 140px; - max-height: 430px; + max-height: 500px; } .note-form-actions { background: transparent; diff --git a/spec/javascripts/behaviors/autosize_spec.js.coffee b/spec/javascripts/behaviors/autosize_spec.js.coffee new file mode 100644 index 00000000000..7fc1d19c35f --- /dev/null +++ b/spec/javascripts/behaviors/autosize_spec.js.coffee @@ -0,0 +1,11 @@ +#= require behaviors/autosize + +describe 'Autosize behavior', -> + beforeEach -> + fixture.set('') + + it 'does not overwrite the resize property', -> + load() + expect($('textarea')).toHaveCss(resize: 'vertical') + + load = -> $(document).trigger('page:load') diff --git a/vendor/assets/javascripts/jquery.ba-resize.js b/vendor/assets/javascripts/jquery.ba-resize.js new file mode 100644 index 00000000000..1f41d379153 --- /dev/null +++ b/vendor/assets/javascripts/jquery.ba-resize.js @@ -0,0 +1,246 @@ +/*! + * jQuery resize event - v1.1 - 3/14/2010 + * http://benalman.com/projects/jquery-resize-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ + +// Script: jQuery resize event +// +// *Version: 1.1, Last updated: 3/14/2010* +// +// Project Home - http://benalman.com/projects/jquery-resize-plugin/ +// GitHub - http://github.com/cowboy/jquery-resize/ +// Source - http://github.com/cowboy/jquery-resize/raw/master/jquery.ba-resize.js +// (Minified) - http://github.com/cowboy/jquery-resize/raw/master/jquery.ba-resize.min.js (1.0kb) +// +// About: License +// +// Copyright (c) 2010 "Cowboy" Ben Alman, +// Dual licensed under the MIT and GPL licenses. +// http://benalman.com/about/license/ +// +// About: Examples +// +// This working example, complete with fully commented code, illustrates a few +// ways in which this plugin can be used. +// +// resize event - http://benalman.com/code/projects/jquery-resize/examples/resize/ +// +// About: Support and Testing +// +// Information about what version or versions of jQuery this plugin has been +// tested with, what browsers it has been tested in, and where the unit tests +// reside (so you can test it yourself). +// +// jQuery Versions - 1.3.2, 1.4.1, 1.4.2 +// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.6, Safari 3-4, Chrome, Opera 9.6-10.1. +// Unit Tests - http://benalman.com/code/projects/jquery-resize/unit/ +// +// About: Release History +// +// 1.1 - (3/14/2010) Fixed a minor bug that was causing the event to trigger +// immediately after bind in some circumstances. Also changed $.fn.data +// to $.data to improve performance. +// 1.0 - (2/10/2010) Initial release + +(function($,window,undefined){ + '$:nomunge'; // Used by YUI compressor. + + // A jQuery object containing all non-window elements to which the resize + // event is bound. + var elems = $([]), + + // Extend $.resize if it already exists, otherwise create it. + jq_resize = $.resize = $.extend( $.resize, {} ), + + timeout_id, + + // Reused strings. + str_setTimeout = 'setTimeout', + str_resize = 'resize', + str_data = str_resize + '-special-event', + str_delay = 'delay', + str_throttle = 'throttleWindow'; + + // Property: jQuery.resize.delay + // + // The numeric interval (in milliseconds) at which the resize event polling + // loop executes. Defaults to 250. + + jq_resize[ str_delay ] = 250; + + // Property: jQuery.resize.throttleWindow + // + // Throttle the native window object resize event to fire no more than once + // every milliseconds. Defaults to true. + // + // Because the window object has its own resize event, it doesn't need to be + // provided by this plugin, and its execution can be left entirely up to the + // browser. However, since certain browsers fire the resize event continuously + // while others do not, enabling this will throttle the window resize event, + // making event behavior consistent across all elements in all browsers. + // + // While setting this property to false will disable window object resize + // event throttling, please note that this property must be changed before any + // window object resize event callbacks are bound. + + jq_resize[ str_throttle ] = true; + + // Event: resize event + // + // Fired when an element's width or height changes. Because browsers only + // provide this event for the window element, for other elements a polling + // loop is initialized, running every milliseconds + // to see if elements' dimensions have changed. You may bind with either + // .resize( fn ) or .bind( "resize", fn ), and unbind with .unbind( "resize" ). + // + // Usage: + // + // > jQuery('selector').bind( 'resize', function(e) { + // > // element's width or height has changed! + // > ... + // > }); + // + // Additional Notes: + // + // * The polling loop is not created until at least one callback is actually + // bound to the 'resize' event, and this single polling loop is shared + // across all elements. + // + // Double firing issue in jQuery 1.3.2: + // + // While this plugin works in jQuery 1.3.2, if an element's event callbacks + // are manually triggered via .trigger( 'resize' ) or .resize() those + // callbacks may double-fire, due to limitations in the jQuery 1.3.2 special + // events system. This is not an issue when using jQuery 1.4+. + // + // > // While this works in jQuery 1.4+ + // > $(elem).css({ width: new_w, height: new_h }).resize(); + // > + // > // In jQuery 1.3.2, you need to do this: + // > var elem = $(elem); + // > elem.css({ width: new_w, height: new_h }); + // > elem.data( 'resize-special-event', { width: elem.width(), height: elem.height() } ); + // > elem.resize(); + + $.event.special[ str_resize ] = { + + // Called only when the first 'resize' event callback is bound per element. + setup: function() { + // Since window has its own native 'resize' event, return false so that + // jQuery will bind the event using DOM methods. Since only 'window' + // objects have a .setTimeout method, this should be a sufficient test. + // Unless, of course, we're throttling the 'resize' event for window. + if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; } + + var elem = $(this); + + // Add this element to the list of internal elements to monitor. + elems = elems.add( elem ); + + // Initialize data store on the element. + $.data( this, str_data, { w: elem.width(), h: elem.height() } ); + + // If this is the first element added, start the polling loop. + if ( elems.length === 1 ) { + loopy(); + } + }, + + // Called only when the last 'resize' event callback is unbound per element. + teardown: function() { + // Since window has its own native 'resize' event, return false so that + // jQuery will unbind the event using DOM methods. Since only 'window' + // objects have a .setTimeout method, this should be a sufficient test. + // Unless, of course, we're throttling the 'resize' event for window. + if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; } + + var elem = $(this); + + // Remove this element from the list of internal elements to monitor. + elems = elems.not( elem ); + + // Remove any data stored on the element. + elem.removeData( str_data ); + + // If this is the last element removed, stop the polling loop. + if ( !elems.length ) { + clearTimeout( timeout_id ); + } + }, + + // Called every time a 'resize' event callback is bound per element (new in + // jQuery 1.4). + add: function( handleObj ) { + // Since window has its own native 'resize' event, return false so that + // jQuery doesn't modify the event object. Unless, of course, we're + // throttling the 'resize' event for window. + if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; } + + var old_handler; + + // The new_handler function is executed every time the event is triggered. + // This is used to update the internal element data store with the width + // and height when the event is triggered manually, to avoid double-firing + // of the event callback. See the "Double firing issue in jQuery 1.3.2" + // comments above for more information. + + function new_handler( e, w, h ) { + var elem = $(this), + data = $.data( this, str_data ); + + // If called from the polling loop, w and h will be passed in as + // arguments. If called manually, via .trigger( 'resize' ) or .resize(), + // those values will need to be computed. + data.w = w !== undefined ? w : elem.width(); + data.h = h !== undefined ? h : elem.height(); + + old_handler.apply( this, arguments ); + }; + + // This may seem a little complicated, but it normalizes the special event + // .add method between jQuery 1.4/1.4.1 and 1.4.2+ + if ( $.isFunction( handleObj ) ) { + // 1.4, 1.4.1 + old_handler = handleObj; + return new_handler; + } else { + // 1.4.2+ + old_handler = handleObj.handler; + handleObj.handler = new_handler; + } + } + + }; + + function loopy() { + + // Start the polling loop, asynchronously. + timeout_id = window[ str_setTimeout ](function(){ + + // Iterate over all elements to which the 'resize' event is bound. + elems.each(function(){ + var elem = $(this), + width = elem.width(), + height = elem.height(), + data = $.data( this, str_data ); + + // If element size has changed since the last time, update the element + // data store and trigger the 'resize' event. + if ( width !== data.w || height !== data.h ) { + elem.trigger( str_resize, [ data.w = width, data.h = height ] ); + } + + }); + + // Loop. + loopy(); + + }, jq_resize[ str_delay ] ); + + }; + +})(jQuery,this); -- cgit v1.2.1