summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG14
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock1
-rw-r--r--app/assets/javascripts/behaviors/autosize.js.coffee20
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss2
-rw-r--r--app/assets/stylesheets/pages/note_form.scss2
-rw-r--r--app/helpers/blob_helper.rb12
-rw-r--r--app/models/merge_request.rb8
-rw-r--r--app/views/admin/application_settings/_form.html.haml6
-rw-r--r--app/views/profiles/two_factor_auths/new.html.haml2
-rw-r--r--app/views/projects/blob/_blob.html.haml5
-rw-r--r--config/initializers/metrics.rb16
-rw-r--r--doc/security/README.md2
-rw-r--r--doc/workflow/gitlab_flow.md7
-rw-r--r--features/project/source/browse_files.feature10
-rw-r--r--features/steps/project/source/browse_files.rb17
-rw-r--r--spec/fixtures/logo_sample.svg27
-rw-r--r--spec/javascripts/behaviors/autosize_spec.js.coffee11
-rw-r--r--spec/models/merge_request_spec.rb22
-rw-r--r--vendor/assets/javascripts/jquery.ba-resize.js246
21 files changed, 398 insertions, 37 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 61c4f1f8903..7a70516173c 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
@@ -14,14 +15,21 @@ 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"
- Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead
- Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead
- Mark inline difference between old and new paths when a file is renamed
v 8.4.3
- - Increase lfs_objects size column to 8-byte integer to allow files larger than 2.1GB
+ - Increase lfs_objects size column to 8-byte integer to allow files larger
+ than 2.1GB
+ - Correctly highlight MR diff when MR has merge conflicts
+ - Fix highlighting in blame view
+ - Update sentry-raven gem to prevent "Not a git repository" console output
+ when running certain commands
v 8.4.2
- Bump required gitlab-workhorse version to bring in a fix for missing
@@ -32,7 +40,6 @@ v 8.4.2
improvement when checking if a repository was empty
- Add instrumentation for Gitlab::Git::Repository instance methods so we can
track them in Performance Monitoring.
- - Correctly highlight MR diff when MR has merge conflicts
- Increase contrast between highlighted code comments and inline diff marker
- Fix method undefined when using external commit status in builds
- Fix highlighting in blame view.
@@ -42,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
@@ -87,7 +93,7 @@ v 8.4.0
- Show 'All' tab by default in the builds page
- Add Open Graph and Twitter Card data to all pages
- Fix API project lookups when querying with a namespace with dots (Stan Hu)
- - Enable forcing Two-Factor authentication sitewide, with optional grace period
+ - Enable forcing Two-factor authentication sitewide, with optional grace period
- Import GitHub Pull Requests into GitLab
- Change single user API endpoint to return more detailed data (Michael Potthoff)
- Update version check images to use SVG
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a6c85e2a6bb..7235ab4a83d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -275,6 +275,8 @@ For examples of feedback on merge requests please look at already
request feel free to mention one of the Merge Marshalls of the [core team][].
Please ensure that your merge request meets the contribution acceptance criteria.
+When having your code reviewed and when reviewing merge requests please take the [thoughtbot code review guidelines](https://github.com/thoughtbot/guides/tree/master/code-review) into account.
+
## Definition of done
If you contribute to GitLab please know that changes involve more than just
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/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/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/models/merge_request.rb b/app/models/merge_request.rb
index 0af60645545..89b6c49b362 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/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index c4fb2accdd0..b0f1a34cbec 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -105,14 +105,14 @@
= f.check_box :signin_enabled
Sign-in enabled
.form-group
- = f.label :two_factor_authentication, 'Two-Factor authentication', class: 'control-label col-sm-2'
+ = f.label :two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2'
.col-sm-10
.checkbox
= f.label :require_two_factor_authentication do
= f.check_box :require_two_factor_authentication
- Require all users to setup Two-Factor authentication
+ Require all users to setup Two-factor authentication
.form-group
- = f.label :two_factor_authentication, 'Two-Factor grace period (hours)', class: 'control-label col-sm-2'
+ = f.label :two_factor_authentication, 'Two-factor grace period (hours)', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :two_factor_grace_period, min: 0, class: 'form-control', placeholder: '0'
.help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication
diff --git a/app/views/profiles/two_factor_auths/new.html.haml b/app/views/profiles/two_factor_auths/new.html.haml
index 1a5b6efce35..b2830aa0834 100644
--- a/app/views/profiles/two_factor_auths/new.html.haml
+++ b/app/views/profiles/two_factor_auths/new.html.haml
@@ -1,6 +1,6 @@
- page_title 'Two-factor Authentication', 'Account'
-%h2.page-title Two-Factor Authentication (2FA)
+%h2.page-title Two-factor Authentication (2FA)
%p
Download the Google Authenticator application from App Store for iOS or Google
Play for Android and scan this code.
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/config/initializers/metrics.rb b/config/initializers/metrics.rb
index 0945b93ed5d..3e1deb8d306 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|
@@ -62,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
diff --git a/doc/security/README.md b/doc/security/README.md
index f34c792d005..be1abb88c3d 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -7,4 +7,4 @@
- [Reset your root password](reset_root_password.md)
- [User File Uploads](user_file_uploads.md)
- [How we manage the CRIME vulnerability](crime_vulnerability.md)
-- [Enforce Two-Factor authentication](two_factor_authentication.md)
+- [Enforce Two-factor authentication](two_factor_authentication.md)
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index 8965e5b3654..be32f0c720b 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -152,9 +152,10 @@ The name of this branch should start with the issue number, for example '15-requ
When you are done or want to discuss the code you open a merge request.
This is an online place to discuss the change and review the code.
-Creating a branch is a manual action since you do not always want to merge a new branch you push, it could be a long-running environment or release branch.
-If you create the merge request but do not assign it to anyone it is a 'work-in-process' merge request.
+Opening a merge request is a manual action since you do not always want to merge a new branch you push, it could be a long-running environment or release branch.
+If you open the merge request but do not assign it to anyone it is a 'Work In Progress' merge request.
These are used to discuss the proposed implementation but are not ready for inclusion in the master branch yet.
+_Pro tip:_ Start the title of the merge request with `[WIP]` or `WIP:` to prevent it from being merged before it's ready.
When the author thinks the code is ready the merge request is assigned to reviewer.
The reviewer presses the merge button when they think the code is ready for inclusion in the master branch.
@@ -185,7 +186,7 @@ If you have an issue that spans across multiple repositories, the best thing is
![Vim screen showing the rebase view](rebase.png)
-With git you can use an interactive rebase (rebase -i) to squash multiple commits into one and reorder them.
+With git you can use an interactive rebase (`rebase -i`) to squash multiple commits into one and reorder them.
This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical.
However you should never rebase commits you have pushed to a remote server.
Somebody can have referred to the commits or cherry-picked them.
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 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="210px" height="210px" viewBox="0 0 210 210" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
+ <!-- Generator: Sketch 3.3.2 (12043) - http://www.bohemiancoding.com/sketch -->
+ <title>Slice 1</title>
+ <desc>Created with Sketch.</desc>
+ <script>alert('FAIL')</script>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
+ <g id="logo" sketch:type="MSLayerGroup" transform="translate(0.000000, 10.000000)">
+ <g id="Page-1" sketch:type="MSShapeGroup">
+ <g id="Fill-1-+-Group-24">
+ <g id="Group-24">
+ <g id="Group">
+ <path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329" class="tanuki-shape"></path>
+ <path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26" class="tanuki-shape"></path>
+ <path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326" class="tanuki-shape"></path>
+ <path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329" class="tanuki-shape"></path>
+ <path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26" class="tanuki-shape"></path>
+ <path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326" class="tanuki-shape"></path>
+ <path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329" class="tanuki-shape"></path>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
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('<textarea class="js-autosize" style="resize: vertical"></textarea>')
+
+ it 'does not overwrite the resize property', ->
+ load()
+ expect($('textarea')).toHaveCss(resize: 'vertical')
+
+ load = -> $(document).trigger('page:load')
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
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 <jQuery.resize.delay> 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 <jQuery.resize.delay> 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);